diff --git a/.env.example b/.env.example index 1c1bb0f8ce..926ef32ab7 100644 --- a/.env.example +++ b/.env.example @@ -6,9 +6,17 @@ DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch JWT_SECRET="secret1233" TOKEN_SALT_COMPLEXITY=10 MAGIC_LINK_TOKEN_VALIDITY= 3 -REFRESH_TOKEN_VALIDITY="604800000" # Default validity is 7 days (604800000 ms) in ms -ACCESS_TOKEN_VALIDITY="86400000" # Default validity is 1 day (86400000 ms) in ms +# Default validity is 7 days (604800000 ms) in ms +REFRESH_TOKEN_VALIDITY="604800000" +# Default validity is 1 day (86400000 ms) in ms +ACCESS_TOKEN_VALIDITY="86400000" SESSION_SECRET='add some secret here' +# Reccomended to be true, set to false if you are using http +# Note: Some auth providers may not support http requests +ALLOW_SECURE_COOKIES=true + +# Sensitive Data Encryption Key while storing in Database (32 character) +DATA_ENCRYPTION_KEY="data encryption key with 32 char" # Hoppscotch App Domain Config REDIRECT_URL="http://localhost:3000" @@ -45,9 +53,20 @@ OIDC_TOKEN_URL="************************************************" OIDC_USERINFO_URL="************************************************" # Mailer config -MAILER_SMTP_URL="smtps://user@domain.com:pass@smtp.domain.com" +MAILER_SMTP_ENABLE="true" +MAILER_USE_CUSTOM_CONFIGS="false" MAILER_ADDRESS_FROM='"From Name Here" ' +MAILER_SMTP_URL="smtps://user@domain.com:pass@smtp.domain.com" # used if custom mailer configs is false + +# The following are used if custom mailer configs is true +MAILER_SMTP_HOST="smtp.domain.com" +MAILER_SMTP_PORT="587" +MAILER_SMTP_SECURE="true" +MAILER_SMTP_USER="user@domain.com" +MAILER_SMTP_PASSWORD="pass" +MAILER_TLS_REJECT_UNAUTHORIZED="true" + # Rate Limit Config RATE_LIMIT_TTL=60 # In seconds RATE_LIMIT_MAX=100 # Max requests per IP diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 850c040983..6c6eacd42a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -7,20 +7,15 @@ Please make sure that the pull request is limited to one type (docs, feature, et Closes # -### Description - + + +### What's changed + -### Checks - -- [ ] My pull request adheres to the code style of this project -- [ ] My code requires changes to the documentation -- [ ] I have updated the documentation as required -- [ ] All the tests have passed - -### Additional Information - +### Notes to reviewers + diff --git a/.github/workflows/build-hoppscotch-agent.yml b/.github/workflows/build-hoppscotch-agent.yml new file mode 100644 index 0000000000..f041f12183 --- /dev/null +++ b/.github/workflows/build-hoppscotch-agent.yml @@ -0,0 +1,249 @@ +on: + workflow_dispatch: + inputs: + version: + description: Tag of the version to build + required: true + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + strategy: + fail-fast: false + matrix: + platform: [macos-latest, ubuntu-22.04, windows-latest] + + runs-on: ${{ matrix.platform }} + defaults: + run: + shell: bash + + steps: + - name: Checkout hoppscotch/hoppscotch + uses: actions/checkout@v3 + with: + repository: hoppscotch/hoppscotch + ref: main + token: ${{ secrets.CHECKOUT_GITHUB_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 9 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Install Rust targets (Mac) + if: matrix.platform == 'macos-latest' + run: | + rustup target add aarch64-apple-darwin + rustup target add x86_64-apple-darwin + + - name: Install additional tools (Linux) + if: matrix.platform == 'ubuntu-22.04' + run: | + # Install Tauri CLI (binary) + curl -LO "https://github.com/tauri-apps/tauri/releases/download/tauri-cli-v2.0.1/cargo-tauri-x86_64-unknown-linux-gnu.tgz" + tar -xzf cargo-tauri-x86_64-unknown-linux-gnu.tgz + chmod +x cargo-tauri + sudo mv cargo-tauri /usr/local/bin/tauri + + # Install Trunk (binary) + curl -LO "https://github.com/thedodd/trunk/releases/download/v0.17.5/trunk-x86_64-unknown-linux-gnu.tar.gz" + tar -xzf trunk-x86_64-unknown-linux-gnu.tar.gz + chmod +x trunk + sudo mv trunk /usr/local/bin/ + + - name: Install additional tools (Mac) + if: matrix.platform == 'macos-latest' + run: | + # Install Tauri CLI (binary) + mkdir __dist/ + cd __dist/ + curl -LO "https://github.com/tauri-apps/tauri/releases/download/tauri-cli-v2.0.1/cargo-tauri-aarch64-apple-darwin.zip" + unzip cargo-tauri-aarch64-apple-darwin.zip + chmod +x cargo-tauri + sudo mv cargo-tauri /usr/local/bin/tauri + + - name: Install system dependencies (Ubuntu only) + if: matrix.platform == 'ubuntu-22.04' + run: | + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.1-dev \ + build-essential \ + curl \ + wget \ + file \ + libxdo-dev \ + libssl-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev + + - name: Setting up Windows Environment and injecting before bundle command (Windows only) + if: matrix.platform == 'windows-latest' + shell: bash + env: + WINDOWS_SIGN_COMMAND: trusted-signing-cli -e ${{ secrets.AZURE_ENDPOINT }} -a ${{ secrets.AZURE_CODE_SIGNING_NAME }} -c ${{ secrets.AZURE_CERT_PROFILE_NAME }} %1 + run: | + cd packages/hoppscotch-agent + # Inject signing command into main conf. + cat './src-tauri/tauri.conf.json' | jq '.bundle .windows += { "signCommand": env.WINDOWS_SIGN_COMMAND}' > './src-tauri/temp.json' && mv './src-tauri/temp.json' './src-tauri/tauri.conf.json' + # Inject signing command into portable conf. + cat './src-tauri/tauri.portable.conf.json' | jq '.bundle .windows += { "signCommand": env.WINDOWS_SIGN_COMMAND}' > './src-tauri/temp_portable.json' && mv './src-tauri/temp_portable.json' './src-tauri/tauri.portable.conf.json' + cargo install trusted-signing-cli@0.3.0 + + - name: Set platform-specific variables + run: | + if [ "${{ matrix.platform }}" = "ubuntu-22.04" ]; then + echo "target_arch=$(rustc -Vv | grep host | awk '{print $2}')" >> $GITHUB_ENV + echo "target_ext=" >> $GITHUB_ENV + echo "target_os_name=linux" >> $GITHUB_ENV + elif [ "${{ matrix.platform }}" = "windows-latest" ]; then + echo "target_arch=x86_64-pc-windows-msvc" >> $GITHUB_ENV + echo "target_ext=.exe" >> $GITHUB_ENV + echo "target_os_name=win" >> $GITHUB_ENV + elif [ "${{ matrix.platform }}" = "macos-latest" ]; then + echo "target_os_name=mac" >> $GITHUB_ENV + fi + + - name: Setup macOS code signing + if: matrix.platform == 'macos-latest' + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 + security create-keychain -p $KEYCHAIN_PASSWORD build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain + security import certificate.p12 -k build.keychain -P $APPLE_CERTIFICATE_PASSWORD -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain + + - name: Cache Rust dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Install dependencies + shell: bash + run: | + cd packages/hoppscotch-agent + pnpm install --filter hoppscotch-agent + + - name: Build Tauri app (Linux) + if: matrix.platform == 'ubuntu-22.04' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.AGENT_TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.AGENT_TAURI_SIGNING_PASSWORD }} + run: | + cd packages/hoppscotch-agent + pnpm tauri build --verbose -b deb -b appimage -b updater + + - name: Build Tauri app (Mac) + if: matrix.platform == 'macos-latest' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.AGENT_TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.AGENT_TAURI_SIGNING_PASSWORD }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + run: | + cd packages/hoppscotch-agent + pnpm tauri build --verbose --target x86_64-apple-darwin + pnpm tauri build --verbose --target aarch64-apple-darwin + + - name: Build Tauri app (Windows) + if: matrix.platform == 'windows-latest' + shell: powershell + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.AGENT_TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.AGENT_TAURI_SIGNING_PASSWORD }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + run: | + cd packages/hoppscotch-agent + # Build the portable version first and move it. + # This way the next build will regenerate `hoppscotch-agent.exe`. + pnpm tauri build --verbose --config src-tauri/tauri.portable.conf.json -- --no-default-features --features portable + Rename-Item -Path "src-tauri/target/release/hoppscotch-agent.exe" -NewName "hoppscotch-agent-portable.exe" + + # Build the installer version. + pnpm tauri build --verbose -b msi -b updater + + - name: Zip portable executable (Windows) + if: matrix.platform == 'windows-latest' + shell: powershell + run: | + Compress-Archive -Path "packages/hoppscotch-agent/src-tauri/target/release/hoppscotch-agent-portable.exe" -DestinationPath "packages/hoppscotch-agent/src-tauri/target/release/Hoppscotch_Agent_win_x64_portable.zip" + + - name: Prepare artifacts + shell: bash + run: | + mkdir artifacts + mkdir artifacts/sigs + if [ "${{ matrix.platform }}" = "ubuntu-22.04" ]; then + mv packages/hoppscotch-agent/src-tauri/target/release/bundle/appimage/*.AppImage artifacts/Hoppscotch_Agent_linux_x64.AppImage + mv packages/hoppscotch-agent/src-tauri/target/release/bundle/appimage/*.AppImage.sig artifacts/sigs/Hoppscotch_Agent_linux_x64.AppImage.sig + mv packages/hoppscotch-agent/src-tauri/target/release/bundle/deb/*.deb artifacts/Hoppscotch_Agent_linux_x64.deb + elif [ "${{ matrix.platform }}" = "macos-latest" ]; then + mv packages/hoppscotch-agent/src-tauri/target/x86_64-apple-darwin/release/bundle/dmg/*_x64.dmg artifacts/Hoppscotch_Agent_mac_x64.dmg + mv packages/hoppscotch-agent/src-tauri/target/x86_64-apple-darwin/release/bundle/macos/*.app.tar.gz artifacts/Hoppscotch_Agent_mac_update_x64.tar.gz + mv packages/hoppscotch-agent/src-tauri/target/x86_64-apple-darwin/release/bundle/macos/*.app.tar.gz.sig artifacts/sigs/Hoppscotch_Agent_mac_update_x64.tar.gz.sig + mv packages/hoppscotch-agent/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*_aarch64.dmg artifacts/Hoppscotch_Agent_mac_aarch64.dmg + mv packages/hoppscotch-agent/src-tauri/target/aarch64-apple-darwin/release/bundle/macos/*.app.tar.gz artifacts/Hoppscotch_Agent_mac_update_aarch64.tar.gz + mv packages/hoppscotch-agent/src-tauri/target/aarch64-apple-darwin/release/bundle/macos/*.app.tar.gz.sig artifacts/sigs/Hoppscotch_Agent_mac_update_aarch64.tar.gz.sig + elif [ "${{ matrix.platform }}" = "windows-latest" ]; then + mv packages/hoppscotch-agent/src-tauri/target/release/bundle/msi/*_x64_en-US.msi artifacts/Hoppscotch_Agent_win_x64.msi + mv packages/hoppscotch-agent/src-tauri/target/release/bundle/msi/*_x64_en-US.msi.sig artifacts/sigs/Hoppscotch_Agent_win_x64.msi.sig + mv packages/hoppscotch-agent/src-tauri/target/release/Hoppscotch_Agent_win_x64_portable.zip artifacts/Hoppscotch_Agent_win_x64_portable.zip + fi + + - name: Generate checksums (Linux) + if: matrix.platform == 'ubuntu-22.04' + run: | + cd artifacts + mkdir shas + for file in *; do + if [ -f "$file" ]; then + sha256sum "$file" > "shas/${file}.sha256" + fi + done + + - name: Generate checksums (Mac) + if: matrix.platform == 'macos-latest' + run: | + cd artifacts + mkdir shas + for file in *; do + if [ -f "$file" ]; then + shasum -a 256 "$file" > "shas/${file}.sha256" + fi + done + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: Hoppscotch_Agent-${{ matrix.platform }} + path: artifacts/* diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ec5e254efa..bb87b6e424 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/release-push-docker.yml b/.github/workflows/release-push-docker.yml index c8f10f968e..6f0bfed918 100644 --- a/.github/workflows/release-push-docker.yml +++ b/.github/workflows/release-push-docker.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup environment run: cp .env.example .env diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9a2af6430d..5d281bca1c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,9 +2,9 @@ name: Node.js CI on: push: - branches: [main, staging, "release/**"] + branches: [main, next, patch] pull_request: - branches: [main, staging, "release/**"] + branches: [main, next, patch] jobs: test: @@ -17,22 +17,21 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup environment run: mv .env.example .env + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - name: Setup pnpm - uses: pnpm/action-setup@v2.2.4 + uses: pnpm/action-setup@v3 with: version: 8 run_install: true - - name: Setup node - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node }} - cache: pnpm - - name: Run tests run: pnpm test diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml index d6ccf93e96..b66ee93fa3 100644 --- a/.github/workflows/ui.yml +++ b/.github/workflows/ui.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup environment run: mv .env.example .env @@ -26,7 +26,7 @@ jobs: run_install: true - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} cache: pnpm diff --git a/.gitignore b/.gitignore index 71152c4629..bdfa619cdb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ pids *.pid *.seed *.pid.lock +*.env # Directory for instrumented libs generated by jscoverage/JSCover lib-cov diff --git a/.husky/commit-msg b/.husky/commit-msg index fe4c17a22d..dab272daff 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - npx --no-install commitlint --edit "" diff --git a/.husky/pre-commit b/.husky/pre-commit index d0612ad370..13823865a7 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npm run pre-commit +npm run pre-commit \ No newline at end of file diff --git a/CODEOWNERS b/CODEOWNERS index e7400a2b6b..5c837665d9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,30 +1,21 @@ # CODEOWNERS is prioritized from bottom to top -# If none of the below matched -* @AndrewBastin @liyasthomas - # Packages /packages/codemirror-lang-graphql/ @AndrewBastin -/packages/hoppscotch-cli/ @AndrewBastin -/packages/hoppscotch-common/ @amk-dev @AndrewBastin +/packages/hoppscotch-cli/ @jamesgeorge007 /packages/hoppscotch-data/ @AndrewBastin -/packages/hoppscotch-js-sandbox/ @AndrewBastin -/packages/hoppscotch-ui/ @anwarulislam -/packages/hoppscotch-web/ @amk-dev -/packages/hoppscotch-selfhost-web/ @amk-dev +/packages/hoppscotch-js-sandbox/ @jamesgeorge007 +/packages/hoppscotch-selfhost-web/ @jamesgeorge007 +/packages/hoppscotch-selfhost-desktop/ @AndrewBastin /packages/hoppscotch-sh-admin/ @JoelJacobStephen -/packages/hoppscotch-backend/ @ankitsridhar16 @balub - -# Sections within Hoppscotch Common -/packages/hoppscotch-common/src/components @anwarulislam -/packages/hoppscotch-common/src/components/collections @nivedin @amk-dev -/packages/hoppscotch-common/src/components/environments @nivedin @amk-dev -/packages/hoppscotch-common/src/composables @amk-dev -/packages/hoppscotch-common/src/modules @AndrewBastin @amk-dev -/packages/hoppscotch-common/src/pages @AndrewBastin @amk-dev -/packages/hoppscotch-common/src/newstore @AndrewBastin @amk-dev +/packages/hoppscotch-backend/ @balub -README.md @liyasthomas +# READMEs and other documentation files +*.md @liyasthomas -# The lockfile has no owner -pnpm-lock.yaml +# Self Host deployment related files +*.Dockerfile @balub +docker-compose.yml @balub +docker-compose.deploy.yml @balub +*.Caddyfile @balub +.dockerignore @balub diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b5d00dc81..ce37ce4b7c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,4 @@ Please note we have a code of conduct, please follow it in all your interactions build. 2. Update the README.md with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters. -3. Increase the version numbers in any examples files and the README.md to the new version that this - Pull Request would represent. The versioning scheme we use is [SemVer](https://semver.org). -4. You may merge the Pull Request once you have the sign-off of two other developers, or if you - do not have permission to do that, you may request the second reviewer merge it for you. +3. Make sure you do not expose environment variables or other sensitive information in your PR. diff --git a/SECURITY.md b/SECURITY.md index f7ab50cdff..8930b000d9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,19 +4,36 @@ This document outlines security procedures and general policies for the Hoppscot - [Security Policy](#security-policy) - [Reporting a security vulnerability](#reporting-a-security-vulnerability) + - [What is not a valid vulnerability](#what-is-not-a-valid-vulnerability) - [Incident response process](#incident-response-process) ## Reporting a security vulnerability -Report security vulnerabilities by emailing the Hoppscotch Support team at support@hoppscotch.io. +We use [Github Security Advisories](https://github.com/hoppscotch/hoppscotch/security/advisories) to manage vulnerability reports and collaboration. +Someone from the Hoppscotch team shall report to you within 48 hours of the disclosure of the vulnerability in GHSA. If no response was received, please reach out to +Hoppscotch Support at support@hoppscotch.io along with the GHSA advisory link. -The primary security point of contact from Hoppscotch Support team will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating the next steps in handling your report. After the initial reply to your report, the security team will endeavor to keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. +> NOTE: Since we have multiple open source components, Advisories may move into the relevant repo (for example, an XSS in a UI component might be part of [`@hoppscotch/ui`](https://github.com/hoppscotch/ui)). +> If in doubt, open your report in `hoppscotch/hoppscotch` GHSA. -**Do not create a GitHub issue ticket to report a security vulnerability.** +**Do not create a GitHub issue ticket to report a security vulnerability!** -The Hoppscotch team and community take all security vulnerability reports in Hoppscotch seriously. Thank you for improving the security of Hoppscotch. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. +The Hoppscotch team takes all security vulnerability reports in Hoppscotch seriously. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. -Report security bugs in third-party modules to the person or team maintaining the module. +## What is not a valid vulnerability +We receive many reports about different sections of the Hoppscotch platform. Hence, we have a fine line we have drawn defining what is considered valid vulnerability. +Please refrain from opening an advisory if it describes the following: + +- A vulnerability in a dependency of Hoppscotch (unless you have practical attack with it on the Hoppscotch codebase) +- Reports of vulnerabilities related to old runtimes (like NodeJS) or container images used by the codebase +- Vulnerabilities present when using Hoppscotch in anything other than the defined minimum requirements that Hoppscotch supports. + +Hoppscotch Team ensures security support for: +- Modern Browsers (Chrome/Firefox/Safari/Edge) with versions up to 1 year old. +- Windows versions on or above Windows 10 on Intel and ARM. +- macOS versions dating back up to 2 years on Intel and Apple Silicon. +- Popular Linux distributions with up-to-date packages with preference to x86/64 CPUs. +- Docker/OCI Runtimes (preference to Docker and Podman) dating back up to 1 year. ## Incident response process diff --git a/aio-multiport-setup.Caddyfile b/aio-multiport-setup.Caddyfile index 70ce1c1f2f..6140f464e0 100644 --- a/aio-multiport-setup.Caddyfile +++ b/aio-multiport-setup.Caddyfile @@ -1,3 +1,8 @@ +{ + admin off + persist_config off +} + :3000 { try_files {path} / root * /site/selfhost-web @@ -13,7 +18,3 @@ :3170 { reverse_proxy localhost:8080 } - -:80 { - respond 404 -} diff --git a/aio-subpath-access.Caddyfile b/aio-subpath-access.Caddyfile index 46a8436e4f..7318eecd26 100644 --- a/aio-subpath-access.Caddyfile +++ b/aio-subpath-access.Caddyfile @@ -1,16 +1,9 @@ -:3000 { - respond 404 +{ + admin off + persist_config off } -:3100 { - respond 404 -} - -:3170 { - reverse_proxy localhost:8080 -} - -:80 { +:{$HOPP_AIO_ALTERNATE_PORT:80} { # Serve the `selfhost-web` SPA by default root * /site/selfhost-web file_server diff --git a/aio_run.mjs b/aio_run.mjs index 59c2684bef..2690078f77 100644 --- a/aio_run.mjs +++ b/aio_run.mjs @@ -51,7 +51,7 @@ fs.rmSync("build.env") const caddyFileName = process.env.ENABLE_SUBPATH_BASED_ACCESS === 'true' ? 'aio-subpath-access.Caddyfile' : 'aio-multiport-setup.Caddyfile' const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", `/etc/caddy/${caddyFileName}`, "--adapter", "caddyfile"], "App/Admin Dashboard Caddy") -const backendProcess = runChildProcessWithPrefix("pnpm", ["run", "start:prod"], "Backend Server") +const backendProcess = runChildProcessWithPrefix("node", ["/dist/backend/dist/main.js"], "Backend Server") caddyProcess.on("exit", (code) => { console.log(`Exiting process because Caddy Server exited with code ${code}`) diff --git a/docker-compose.deploy.yml b/docker-compose.deploy.yml index 3e2028f7d8..ccf9f93e83 100644 --- a/docker-compose.deploy.yml +++ b/docker-compose.deploy.yml @@ -1,8 +1,6 @@ # THIS IS NOT TO BE USED FOR PERSONAL DEPLOYMENTS! # Internal Docker Compose Image used for internal testing deployments -version: "3.7" - services: hoppscotch-db: image: postgres:15 @@ -28,7 +26,9 @@ services: context: . target: aio environment: - - DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch + # DATABASE_URL is read from the .env file to allow the backend to connect with an external database. + # This allows the backend to retain existing data and prevents database resets during deployments. + # DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch - ENABLE_SUBPATH_BASED_ACCESS=true env_file: - ./.env diff --git a/docker-compose.yml b/docker-compose.yml index b7f3d2f52f..ee5bd337a1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,6 @@ # To make it easier to self-host, we have a preset docker compose config that also # has a container with a Postgres instance running. # You can tweak around this file to match your instances -version: "3.7" services: # This service runs the backend app in the port 3170 @@ -100,7 +99,7 @@ services: test: [ "CMD-SHELL", - "sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'" + "sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'", ] interval: 5s timeout: 5s diff --git a/healthcheck.sh b/healthcheck.sh index c772d6bf55..c950768993 100644 --- a/healthcheck.sh +++ b/healthcheck.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh curlCheck() { if ! curl -s --head "$1" | head -n 1 | grep -q "HTTP/1.[01] [23].."; then diff --git a/package.json b/package.json index 56f13211e2..b142bd48ca 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "license": "MIT", "scripts": { "preinstall": "npx only-allow pnpm", - "prepare": "husky install", + "prepare": "husky", "dev": "pnpm -r do-dev", "gen-gql": "cross-env GQL_SCHEMA_EMIT_LOCATION='../../../gql-gen/backend-schema.gql' pnpm -r generate-gql-sdl", "generate": "pnpm -r do-build-prod", @@ -23,22 +23,33 @@ "./packages/*" ], "devDependencies": { - "@commitlint/cli": "16.3.0", - "@commitlint/config-conventional": "16.2.4", - "@hoppscotch/ui": "0.1.0", - "@types/node": "17.0.27", + "@commitlint/cli": "19.5.0", + "@commitlint/config-conventional": "19.5.0", + "@hoppscotch/ui": "0.2.2", + "@types/node": "22.7.6", "cross-env": "7.0.3", "http-server": "14.1.1", - "husky": "7.0.4", - "lint-staged": "12.4.0" + "husky": "9.1.6", + "lint-staged": "15.2.10" }, "pnpm": { "overrides": { - "vue": "3.3.9" + "cookie": "0.7.2", + "cross-spawn": "7.0.6", + "vue": "3.5.12", + "@nestjs-modules/mailer>mjml": "5.0.0-alpha.4", + "subscriptions-transport-ws>ws": "7.5.10", + "braces": "3.0.3", + "send": "0.19.0", + "pug": "3.0.3", + "body-parser": "1.20.3", + "path-to-regexp@3.2.0": "3.3.0", + "micromatch@<4.0.8": "4.0.8", + "dset@3.1.3": "3.1.4" }, "packageExtensions": { - "httpsnippet@3.0.1": { - "peerDependencies": { + "@hoppscotch/httpsnippet": { + "dependencies": { "ajv": "6.12.3" } } diff --git a/packages/codemirror-lang-graphql/package.json b/packages/codemirror-lang-graphql/package.json index c5fb23f70d..d322bc2c2e 100644 --- a/packages/codemirror-lang-graphql/package.json +++ b/packages/codemirror-lang-graphql/package.json @@ -5,7 +5,7 @@ "author": "Hoppscotch (support@hoppscotch.io)", "license": "MIT", "scripts": { - "prepare": "rollup -c" + "prepare": "rollup -c && tsc --emitDeclarationOnly --declaration" }, "type": "module", "main": "dist/index.cjs", @@ -25,8 +25,7 @@ "@lezer/generator": "1.5.1", "mocha": "9.2.2", "rollup": "3.29.4", - "rollup-plugin-dts": "6.0.2", - "rollup-plugin-ts": "3.4.5", + "@rollup/plugin-typescript": "12.1.1", "typescript": "5.2.2" } -} \ No newline at end of file +} diff --git a/packages/codemirror-lang-graphql/rollup.config.js b/packages/codemirror-lang-graphql/rollup.config.js index 44dffbd51f..d19001c396 100644 --- a/packages/codemirror-lang-graphql/rollup.config.js +++ b/packages/codemirror-lang-graphql/rollup.config.js @@ -1,4 +1,4 @@ -import typescript from "rollup-plugin-ts" +import typescript from "@rollup/plugin-typescript" import { lezer } from "@lezer/generator/rollup" export default { @@ -8,5 +8,10 @@ export default { { file: "dist/index.cjs", format: "cjs" }, { dir: "./dist", format: "es" }, ], - plugins: [lezer(), typescript()], + plugins: [ + lezer(), + typescript({ + tsconfig: "./tsconfig.json" + }) + ], } diff --git a/packages/codemirror-lang-graphql/tsconfig.json b/packages/codemirror-lang-graphql/tsconfig.json index 7eaca8b2ed..b258d4ce07 100644 --- a/packages/codemirror-lang-graphql/tsconfig.json +++ b/packages/codemirror-lang-graphql/tsconfig.json @@ -5,6 +5,7 @@ "module": "es2020", "newLine": "lf", "declaration": true, + "declarationDir": "./dist", "moduleResolution": "node", "allowJs": true }, diff --git a/packages/hoppscotch-agent/.envrc b/packages/hoppscotch-agent/.envrc new file mode 100644 index 0000000000..5bf8fc159d --- /dev/null +++ b/packages/hoppscotch-agent/.envrc @@ -0,0 +1,3 @@ +source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0=" + +use devenv \ No newline at end of file diff --git a/packages/hoppscotch-agent/.gitignore b/packages/hoppscotch-agent/.gitignore new file mode 100644 index 0000000000..e9f02db4ed --- /dev/null +++ b/packages/hoppscotch-agent/.gitignore @@ -0,0 +1,33 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/packages/hoppscotch-agent/README.md b/packages/hoppscotch-agent/README.md new file mode 100644 index 0000000000..5047afc643 --- /dev/null +++ b/packages/hoppscotch-agent/README.md @@ -0,0 +1,16 @@ +# Tauri + Vue + TypeScript + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/packages/hoppscotch-agent/package.json b/packages/hoppscotch-agent/package.json new file mode 100644 index 0000000000..19b6f64e11 --- /dev/null +++ b/packages/hoppscotch-agent/package.json @@ -0,0 +1,34 @@ +{ + "name": "hoppscotch-agent", + "private": true, + "version": "0.1.3", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@hoppscotch/ui": "^0.2.1", + "@tauri-apps/api": "^2.0.2", + "@tauri-apps/plugin-shell": "^2.0.0", + "@vueuse/core": "^11.1.0", + "axios": "^1.7.7", + "fp-ts": "^2.16.9", + "vue": "3.3.9" + }, + "devDependencies": { + "@iconify-json/lucide": "^1.2.8", + "@tauri-apps/cli": "^2.0.3", + "@types/node": "^22.7.5", + "@vitejs/plugin-vue": "^5.1.4", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.13", + "typescript": "^5.6.3", + "unplugin-icons": "^0.19.3", + "vite": "^5.4.8", + "vue-tsc": "^2.1.6" + } +} diff --git a/packages/hoppscotch-agent/pnpm-lock.yaml b/packages/hoppscotch-agent/pnpm-lock.yaml new file mode 100644 index 0000000000..f64dd66fd2 --- /dev/null +++ b/packages/hoppscotch-agent/pnpm-lock.yaml @@ -0,0 +1,2952 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@hoppscotch/ui': + specifier: ^0.2.1 + version: 0.2.1(eslint@9.11.1(jiti@1.21.6))(terser@5.34.0)(typescript@5.6.2)(vite@5.4.7(@types/node@22.7.0)(terser@5.34.0))(vue@3.5.8(typescript@5.6.2)) + '@tauri-apps/api': + specifier: '>=2.0.0-rc.0' + version: 2.0.0-rc.5 + '@tauri-apps/plugin-shell': + specifier: '>=2.0.0-rc.0' + version: 2.0.0-rc.1 + '@vueuse/core': + specifier: ^11.1.0 + version: 11.1.0(vue@3.5.8(typescript@5.6.2)) + axios: + specifier: ^1.7.7 + version: 1.7.7 + fp-ts: + specifier: ^2.16.9 + version: 2.16.9 + vue: + specifier: ^3.3.4 + version: 3.5.8(typescript@5.6.2) + devDependencies: + '@iconify-json/lucide': + specifier: ^1.2.6 + version: 1.2.6 + '@tauri-apps/cli': + specifier: '>=2.0.0-rc.0' + version: 2.0.0-rc.16 + '@types/node': + specifier: ^22.7.0 + version: 22.7.0 + '@vitejs/plugin-vue': + specifier: ^5.1.4 + version: 5.1.4(vite@5.4.7(@types/node@22.7.0)(terser@5.34.0))(vue@3.5.8(typescript@5.6.2)) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.4.47) + postcss: + specifier: ^8.4.47 + version: 8.4.47 + tailwindcss: + specifier: ^3.4.13 + version: 3.4.13 + typescript: + specifier: ^5.2.2 + version: 5.6.2 + unplugin-icons: + specifier: ^0.19.3 + version: 0.19.3(@vue/compiler-sfc@3.5.8) + vite: + specifier: ^5.4.7 + version: 5.4.7(@types/node@22.7.0)(terser@5.34.0) + vue-tsc: + specifier: ^2.0.22 + version: 2.1.6(typescript@5.6.2) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@antfu/install-pkg@0.4.1': + resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==} + + '@antfu/utils@0.7.10': + resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + + '@babel/helper-string-parser@7.24.8': + resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.25.6': + resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/standalone@7.25.6': + resolution: {integrity: sha512-Kf2ZcZVqsKbtYhlA7sP0z5A3q5hmCVYMKMWRWNK/5OVwHIve3JY1djVRmIVAx8FMueLIfZGKQDIILK2w8zO4mg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.25.6': + resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} + engines: {node: '>=6.9.0'} + + '@boringer-avatars/vue3@0.2.1': + resolution: {integrity: sha512-KzAfh31SDXToTvFL0tBNG5Ur+VzfD1PP4jmY5/GS+eIuObGTIAiUu9eiht0LjuAGI+0xCgnaEgsTrOx8H3vLOQ==} + peerDependencies: + vue: ^3.0.0 + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.11.1': + resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.18.0': + resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.6.0': + resolution: {integrity: sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.1.0': + resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.11.1': + resolution: {integrity: sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.4': + resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.0': + resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@fontsource-variable/inter@5.1.0': + resolution: {integrity: sha512-Wj2dUGP0vUpxRGQTXQTCNJO+aLcFcQm+gUPXfj/aS877bQkEPBPv9JvZJpwdm2vzelt8NTZ+ausKlBCJjh2XIg==} + + '@fontsource-variable/material-symbols-rounded@5.1.0': + resolution: {integrity: sha512-gQZli8YyPTymXuAzMQ13d6Fzw9zQkMXIxqZyCrSeNLKfOpLVKXoh0LZ0SV9thOpZhR2KQURD8S1Y0YLUV6KZNw==} + + '@fontsource-variable/roboto-mono@5.1.0': + resolution: {integrity: sha512-87USlDpEi7dS/ayPXwf/08vdgJKEYxdQmuZk5kCzWBtumimdwWlA9Vh36TCtYqFC+dGgDxPX/4PJK+0lxkEd5A==} + + '@hoppscotch/ui@0.2.1': + resolution: {integrity: sha512-orwItZFlOZUFfMf0B7RNAQa2ByucnXcl9ufP0aJggyFafRk/X9eyMPC6JrF/OATml/ZztksjiB2636cMYsEnSw==} + engines: {node: '>=16'} + peerDependencies: + vue: ^3.2.25 + + '@hoppscotch/vue-sonner@1.2.3': + resolution: {integrity: sha512-P1gyvHHLsPeB8lsLP5SrqwQatuwOKtbsP83sKhyIV3WL2rJj3+DiFfqo2ErNBa+Sl0gM68o1V+wuOS7zbR//6g==} + + '@hoppscotch/vue-toasted@0.1.0': + resolution: {integrity: sha512-DIgmeTHxWwX5UeaHLEqDYNLJFGRosx/5N1fCHkaO8zt+sZv8GrHlkrIpjfKF2drmA3kKw5cY42Cw7WuCoabR3g==} + peerDependencies: + vue: ^3.2.37 + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.0': + resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + engines: {node: '>=18.18'} + + '@iconify-json/lucide@1.2.6': + resolution: {integrity: sha512-HVLLxmG/rJ91GqpEE97OHdEwb/LfjwMVdynWIrnjvNRQNoKvrYG2N2+jABSBibEhnYQOfv+k8/7X6fK44PEtKA==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@2.1.33': + resolution: {integrity: sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + + '@rollup/rollup-android-arm-eabi@4.22.4': + resolution: {integrity: sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.22.4': + resolution: {integrity: sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.22.4': + resolution: {integrity: sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.22.4': + resolution: {integrity: sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.22.4': + resolution: {integrity: sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.22.4': + resolution: {integrity: sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.22.4': + resolution: {integrity: sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.22.4': + resolution: {integrity: sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.22.4': + resolution: {integrity: sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.22.4': + resolution: {integrity: sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.22.4': + resolution: {integrity: sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.22.4': + resolution: {integrity: sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.22.4': + resolution: {integrity: sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.22.4': + resolution: {integrity: sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.22.4': + resolution: {integrity: sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.22.4': + resolution: {integrity: sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==} + cpu: [x64] + os: [win32] + + '@tauri-apps/api@2.0.0-rc.5': + resolution: {integrity: sha512-JWs69pE9NsQdcqTpEVBepAZ08+jgQWuthIiTiaWRq/YlCLgnqq0KfC9sDem55uJ0YpgsytZuyj+m6b6q8oiD2g==} + + '@tauri-apps/cli-darwin-arm64@2.0.0-rc.16': + resolution: {integrity: sha512-lISZU4gG0c9PbY7h/j/gW7nJLxZEygNBrYEET6zN8R99Znf5rSO+CfjenaMcJUUj6yTAd8gzdakRpLqNSAWegA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tauri-apps/cli-darwin-x64@2.0.0-rc.16': + resolution: {integrity: sha512-D9uxPCxpyYcTSQulJGFX3POAKPOJd8WcWHFH8x6YVM1cIx6EWRXIE1sZnPUOjFr7qCg+bSdYdr8/BFHcZGcApQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-rc.16': + resolution: {integrity: sha512-WsVdKm4D1I1XV8I9yRnmOINZRwwWfh6xcLV3m19+B9g6TohK8RkRxWfxvs3TLQlWOLQ6lo8BzS9rzXB+KtjDpg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tauri-apps/cli-linux-arm64-gnu@2.0.0-rc.16': + resolution: {integrity: sha512-2jpZDagNs6rrqposHJihHBayttgOl5aB2+bYiVEC6ye+haiFtmYmpdaPOaVIw+YVQs6lepf5PVrisCoU9DmYsg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-arm64-musl@2.0.0-rc.16': + resolution: {integrity: sha512-SNEDcB+sWOls/B0a+UpUHVa/oegvlXXKYWsTxuXtgWIr5VbWG7rXLZ3fZpLLP3SpRVGTGTnABcyqshFbWSqqKQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-x64-gnu@2.0.0-rc.16': + resolution: {integrity: sha512-Zsq29MM1ooeH1+chQBa7ffDFnzAZebBBFdsvs4e05tS1H8gn4oKE+PSMn9p/okzVXykEk9ri2/n7BG1XFeifMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-linux-x64-musl@2.0.0-rc.16': + resolution: {integrity: sha512-g+pwSuis2YMxhJJ/pJYwp/Nps5CWvlv/5MV5UfDvClkCkeAyzIqVX+HbBLPcs5S0CePUQNeP0j4d4jBWUqZZQg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-win32-arm64-msvc@2.0.0-rc.16': + resolution: {integrity: sha512-PpPqdMTwJSDAK4KnNjvh77ShSkY+7pih1f6e50EtXar8bjC17e3XcEqFhDNne5mxEVTLYhibs6p1JLPad0ZjRA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tauri-apps/cli-win32-ia32-msvc@2.0.0-rc.16': + resolution: {integrity: sha512-io2yIcEcG7YLP+9n13NbilB93SjcB7jIl8GbURC4XZT4/4t9D1PWHpJr5hySVsGRLCz5e8NzwC5RlnenNzmpPQ==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@tauri-apps/cli-win32-x64-msvc@2.0.0-rc.16': + resolution: {integrity: sha512-Rfkmxe3k+cBVA/kVYt8O25QrQqWKJlH9AiH7Q3C6xBzzG9PCSRMBszCp+JhBF8jhVlwNmOBv6UG+lm85kspwGg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tauri-apps/cli@2.0.0-rc.16': + resolution: {integrity: sha512-wdjZg/M3dcxiqgWG6VRnABpX0dYxRww93t0d1MYoZxFDrnyoUz5kYwFQ0v4J9u0qenEgskjoypvon7V/Nj9qrg==} + engines: {node: '>= 10'} + hasBin: true + + '@tauri-apps/plugin-shell@2.0.0-rc.1': + resolution: {integrity: sha512-JtNROc0rqEwN/g93ig5pK4cl1vUo2yn+osCpY9de64cy/d9hRzof7AuYOgvt/Xcd5VPQmlgo2AGvUh5sQRSR1A==} + + '@types/eslint@8.56.12': + resolution: {integrity: sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@22.7.0': + resolution: {integrity: sha512-MOdOibwBs6KW1vfqz2uKMlxq5xAfAZ98SZjO8e3XnAbFnTJtAspqhWk7hrdSAs9/Y14ZWMiy7/MxMUzAOadYEw==} + + '@types/web-bluetooth@0.0.14': + resolution: {integrity: sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A==} + + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + + '@vitejs/plugin-legacy@2.3.1': + resolution: {integrity: sha512-J5KaGBlSt2tEYPVjM/C8dA6DkRzkFkbPe+Xb4IX5G+XOV5OGbVAfkMjKywdrkO3gGynO8S98i71Lmsff4cWkCQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + terser: ^5.4.0 + vite: ^3.0.0 + + '@vitejs/plugin-vue@5.1.4': + resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.5': + resolution: {integrity: sha512-F4tA0DCO5Q1F5mScHmca0umsi2ufKULAnMOVBfMsZdT4myhVl4WdKRwCaKcfOkIEuyrAVvtq1ESBdZ+rSyLVww==} + + '@volar/source-map@2.4.5': + resolution: {integrity: sha512-varwD7RaKE2J/Z+Zu6j3mNNJbNT394qIxXwdvz/4ao/vxOfyClZpSDtLKkwWmecinkOVos5+PWkWraelfMLfpw==} + + '@volar/typescript@2.4.5': + resolution: {integrity: sha512-mcT1mHvLljAEtHviVcBuOyAwwMKz1ibXTi5uYtP/pf4XxoAzpdkQ+Br2IC0NPCvLCbjPZmbf3I0udndkfB1CDg==} + + '@vue/compiler-core@3.5.8': + resolution: {integrity: sha512-Uzlxp91EPjfbpeO5KtC0KnXPkuTfGsNDeaKQJxQN718uz+RqDYarEf7UhQJGK+ZYloD2taUbHTI2J4WrUaZQNA==} + + '@vue/compiler-dom@3.5.8': + resolution: {integrity: sha512-GUNHWvoDSbSa5ZSHT9SnV5WkStWfzJwwTd6NMGzilOE/HM5j+9EB9zGXdtu/fCNEmctBqMs6C9SvVPpVPuk1Eg==} + + '@vue/compiler-sfc@3.5.8': + resolution: {integrity: sha512-taYpngQtSysrvO9GULaOSwcG5q821zCoIQBtQQSx7Uf7DxpR6CIHR90toPr9QfDD2mqHQPCSgoWBvJu0yV9zjg==} + + '@vue/compiler-ssr@3.5.8': + resolution: {integrity: sha512-W96PtryNsNG9u0ZnN5Q5j27Z/feGrFV6zy9q5tzJVyJaLiwYxvC0ek4IXClZygyhjm+XKM7WD9pdKi/wIRVC/Q==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/language-core@2.1.6': + resolution: {integrity: sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.8': + resolution: {integrity: sha512-mlgUyFHLCUZcAYkqvzYnlBRCh0t5ZQfLYit7nukn1GR96gc48Bp4B7OIcSfVSvlG1k3BPfD+p22gi1t2n9tsXg==} + + '@vue/runtime-core@3.5.8': + resolution: {integrity: sha512-fJuPelh64agZ8vKkZgp5iCkPaEqFJsYzxLk9vSC0X3G8ppknclNDr61gDc45yBGTaN5Xqc1qZWU3/NoaBMHcjQ==} + + '@vue/runtime-dom@3.5.8': + resolution: {integrity: sha512-DpAUz+PKjTZPUOB6zJgkxVI3GuYc2iWZiNeeHQUw53kdrparSTG6HeXUrYDjaam8dVsCdvQxDz6ZWxnyjccUjQ==} + + '@vue/server-renderer@3.5.8': + resolution: {integrity: sha512-7AmC9/mEeV9mmXNVyUIm1a1AjUhyeeGNbkLh39J00E7iPeGks8OGRB5blJiMmvqSh8SkaS7jkLWSpXtxUCeagA==} + peerDependencies: + vue: 3.5.8 + + '@vue/shared@3.5.8': + resolution: {integrity: sha512-mJleSWbAGySd2RJdX1RBtcrUBX6snyOc0qHpgk3lGi4l9/P/3ny3ELqFWqYdkXIwwNN/kdm8nD9ky8o6l/Lx2A==} + + '@vueuse/core@11.1.0': + resolution: {integrity: sha512-P6dk79QYA6sKQnghrUz/1tHi0n9mrb/iO1WTMk/ElLmTyNqgDeSZ3wcDf6fRBGzRJbeG1dxzEOvLENMjr+E3fg==} + + '@vueuse/core@8.9.4': + resolution: {integrity: sha512-B/Mdj9TK1peFyWaPof+Zf/mP9XuGAngaJZBwPaXBvU3aCTZlx3ltlrFFFyMV4iGBwsjSCeUCgZrtkEj9dS2Y3Q==} + peerDependencies: + '@vue/composition-api': ^1.1.0 + vue: ^2.6.0 || ^3.2.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + vue: + optional: true + + '@vueuse/metadata@11.1.0': + resolution: {integrity: sha512-l9Q502TBTaPYGanl1G+hPgd3QX5s4CGnpXriVBR5fEZ/goI6fvDaVmIl3Td8oKFurOxTmbXvBPSsgrd6eu6HYg==} + + '@vueuse/metadata@8.9.4': + resolution: {integrity: sha512-IwSfzH80bnJMzqhaapqJl9JRIiyQU0zsRGEgnxN6jhq7992cPUJIRfV+JHRIZXjYqbwt07E1gTEp0R0zPJ1aqw==} + + '@vueuse/shared@11.1.0': + resolution: {integrity: sha512-YUtIpY122q7osj+zsNMFAfMTubGz0sn5QzE5gPzAIiCmtt2ha3uQUY1+JPyL4gRCTsLPX82Y9brNbo/aqlA91w==} + + '@vueuse/shared@8.9.4': + resolution: {integrity: sha512-wt+T30c4K6dGRMVqPddexEVLa28YwxW5OFIPmzUHICjphfAuBFTTdDoyqREZNDOFJZ44ARH1WWQNCUK8koJ+Ag==} + peerDependencies: + '@vue/composition-api': ^1.1.0 + vue: ^2.6.0 || ^3.2.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + vue: + optional: true + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.0: + resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001663: + resolution: {integrity: sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + computeds@0.0.1: + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} + + concat-map@0.0.1: + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + + confbox@0.1.7: + resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + + core-js@3.38.1: + resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.28: + resolution: {integrity: sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@8.0.2: + resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.0.0: + resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.11.1: + resolution: {integrity: sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.1.0: + resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + + fp-ts@2.16.9: + resolution: {integrity: sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ==} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + magic-string@0.26.7: + resolution: {integrity: sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==} + engines: {node: '>=12'} + + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.7.1: + resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + + package-manager-detector@0.2.0: + resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path@0.12.7: + resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + pkg-types@1.2.0: + resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@2.79.1: + resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} + engines: {node: '>=10.0.0'} + hasBin: true + + rollup@4.22.4: + resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sortablejs@1.14.0: + resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + systemjs@6.15.1: + resolution: {integrity: sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==} + + tailwindcss@3.4.13: + resolution: {integrity: sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==} + engines: {node: '>=14.0.0'} + hasBin: true + + terser@5.34.0: + resolution: {integrity: sha512-y5NUX+U9HhVsK/zihZwoq4r9dICLyV2jXGOriDAVOeKhq3LKVjgJbGO90FisozXLlJfvjHqgckGmJFBb9KYoWQ==} + engines: {node: '>=10'} + hasBin: true + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinyexec@0.3.0: + resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript@5.6.2: + resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + unplugin-icons@0.19.3: + resolution: {integrity: sha512-EUegRmsAI6+rrYr0vXjFlIP+lg4fSC4zb62zAZKx8FGXlWAGgEGBCa3JDe27aRAXhistObLPbBPhwa/0jYLFkQ==} + peerDependencies: + '@svgr/core': '>=7.0.0' + '@svgx/core': ^1.0.1 + '@vue/compiler-sfc': ^3.0.2 || ^2.7.0 + vue-template-compiler: ^2.6.12 + vue-template-es2015-compiler: ^1.9.0 + peerDependenciesMeta: + '@svgr/core': + optional: true + '@svgx/core': + optional: true + '@vue/compiler-sfc': + optional: true + vue-template-compiler: + optional: true + vue-template-es2015-compiler: + optional: true + + unplugin@1.14.1: + resolution: {integrity: sha512-lBlHbfSFPToDYp9pjXlUEFVxYLaue9f9T1HC+4OHlmj+HnMDdz9oZY+erXfoCe/5V/7gKUSY2jpXPb9S7f0f/w==} + engines: {node: '>=14.0.0'} + peerDependencies: + webpack-sources: ^3 + peerDependenciesMeta: + webpack-sources: + optional: true + + update-browserslist-db@1.1.0: + resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + util@0.10.4: + resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==} + + vite-plugin-eslint@1.8.1: + resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==} + peerDependencies: + eslint: '>=7' + vite: '>=2' + + vite@5.4.7: + resolution: {integrity: sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-promise-modals@0.1.0: + resolution: {integrity: sha512-LmPejeqvZSkxj4KkJe6ZUEJmCUQXVeEAj9ihTX+BRFfZftVCZSZd3B4uuZSKF0iCeQUemkodXUZFxcsNT/2dmg==} + + vue-tsc@2.1.6: + resolution: {integrity: sha512-f98dyZp5FOukcYmbFpuSCJ4Z0vHSOSmxGttZJCsFeX0M4w/Rsq0s4uKXjcSRsZqsRgQa6z7SfuO+y0HVICE57Q==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.8: + resolution: {integrity: sha512-hvuvuCy51nP/1fSRvrrIqTLSvrSyz2Pq+KQ8S8SXCxTWVE0nMaOnSDnSOxV1eYmGfvK7mqiwvd1C59CEEz7dAQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vuedraggable-es@4.1.1: + resolution: {integrity: sha512-F35pjSwC8HS/lnaOd+B59nYR4FZmwuhWAzccK9xftRuWds8SU1TZh5myKVM86j5dFOI7S26O64Kwe7LUHnXjlA==} + peerDependencies: + vue: ^3.2.31 + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yaml@2.5.1: + resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} + engines: {node: '>= 14'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@antfu/install-pkg@0.4.1': + dependencies: + package-manager-detector: 0.2.0 + tinyexec: 0.3.0 + + '@antfu/utils@0.7.10': {} + + '@babel/helper-string-parser@7.24.8': {} + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/parser@7.25.6': + dependencies: + '@babel/types': 7.25.6 + + '@babel/standalone@7.25.6': {} + + '@babel/types@7.25.6': + dependencies: + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + + '@boringer-avatars/vue3@0.2.1(vue@3.5.8(typescript@5.6.2))': + dependencies: + vue: 3.5.8(typescript@5.6.2) + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@9.11.1(jiti@1.21.6))': + dependencies: + eslint: 9.11.1(jiti@1.21.6) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.11.1': {} + + '@eslint/config-array@0.18.0': + dependencies: + '@eslint/object-schema': 2.1.4 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.6.0': {} + + '@eslint/eslintrc@3.1.0': + dependencies: + ajv: 6.12.6 + debug: 4.3.7 + espree: 10.1.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.11.1': {} + + '@eslint/object-schema@2.1.4': {} + + '@eslint/plugin-kit@0.2.0': + dependencies: + levn: 0.4.1 + + '@fontsource-variable/inter@5.1.0': {} + + '@fontsource-variable/material-symbols-rounded@5.1.0': {} + + '@fontsource-variable/roboto-mono@5.1.0': {} + + '@hoppscotch/ui@0.2.1(eslint@9.11.1(jiti@1.21.6))(terser@5.34.0)(typescript@5.6.2)(vite@5.4.7(@types/node@22.7.0)(terser@5.34.0))(vue@3.5.8(typescript@5.6.2))': + dependencies: + '@boringer-avatars/vue3': 0.2.1(vue@3.5.8(typescript@5.6.2)) + '@fontsource-variable/inter': 5.1.0 + '@fontsource-variable/material-symbols-rounded': 5.1.0 + '@fontsource-variable/roboto-mono': 5.1.0 + '@hoppscotch/vue-sonner': 1.2.3 + '@hoppscotch/vue-toasted': 0.1.0(vue@3.5.8(typescript@5.6.2)) + '@vitejs/plugin-legacy': 2.3.1(terser@5.34.0)(vite@5.4.7(@types/node@22.7.0)(terser@5.34.0)) + '@vueuse/core': 8.9.4(vue@3.5.8(typescript@5.6.2)) + fp-ts: 2.16.9 + lodash-es: 4.17.21 + path: 0.12.7 + vite-plugin-eslint: 1.8.1(eslint@9.11.1(jiti@1.21.6))(vite@5.4.7(@types/node@22.7.0)(terser@5.34.0)) + vue: 3.5.8(typescript@5.6.2) + vue-promise-modals: 0.1.0(typescript@5.6.2) + vuedraggable-es: 4.1.1(vue@3.5.8(typescript@5.6.2)) + transitivePeerDependencies: + - '@vue/composition-api' + - eslint + - terser + - typescript + - vite + + '@hoppscotch/vue-sonner@1.2.3': {} + + '@hoppscotch/vue-toasted@0.1.0(vue@3.5.8(typescript@5.6.2))': + dependencies: + vue: 3.5.8(typescript@5.6.2) + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.0': {} + + '@iconify-json/lucide@1.2.6': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@2.1.33': + dependencies: + '@antfu/install-pkg': 0.4.1 + '@antfu/utils': 0.7.10 + '@iconify/types': 2.0.0 + debug: 4.3.7 + kolorist: 1.8.0 + local-pkg: 0.5.0 + mlly: 1.7.1 + transitivePeerDependencies: + - supports-color + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/pluginutils@4.2.1': + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + + '@rollup/rollup-android-arm-eabi@4.22.4': + optional: true + + '@rollup/rollup-android-arm64@4.22.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.22.4': + optional: true + + '@rollup/rollup-darwin-x64@4.22.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.22.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.22.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.22.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.22.4': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.22.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.22.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.22.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.22.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.22.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.22.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.22.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.22.4': + optional: true + + '@tauri-apps/api@2.0.0-rc.5': {} + + '@tauri-apps/cli-darwin-arm64@2.0.0-rc.16': + optional: true + + '@tauri-apps/cli-darwin-x64@2.0.0-rc.16': + optional: true + + '@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-rc.16': + optional: true + + '@tauri-apps/cli-linux-arm64-gnu@2.0.0-rc.16': + optional: true + + '@tauri-apps/cli-linux-arm64-musl@2.0.0-rc.16': + optional: true + + '@tauri-apps/cli-linux-x64-gnu@2.0.0-rc.16': + optional: true + + '@tauri-apps/cli-linux-x64-musl@2.0.0-rc.16': + optional: true + + '@tauri-apps/cli-win32-arm64-msvc@2.0.0-rc.16': + optional: true + + '@tauri-apps/cli-win32-ia32-msvc@2.0.0-rc.16': + optional: true + + '@tauri-apps/cli-win32-x64-msvc@2.0.0-rc.16': + optional: true + + '@tauri-apps/cli@2.0.0-rc.16': + optionalDependencies: + '@tauri-apps/cli-darwin-arm64': 2.0.0-rc.16 + '@tauri-apps/cli-darwin-x64': 2.0.0-rc.16 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0-rc.16 + '@tauri-apps/cli-linux-arm64-gnu': 2.0.0-rc.16 + '@tauri-apps/cli-linux-arm64-musl': 2.0.0-rc.16 + '@tauri-apps/cli-linux-x64-gnu': 2.0.0-rc.16 + '@tauri-apps/cli-linux-x64-musl': 2.0.0-rc.16 + '@tauri-apps/cli-win32-arm64-msvc': 2.0.0-rc.16 + '@tauri-apps/cli-win32-ia32-msvc': 2.0.0-rc.16 + '@tauri-apps/cli-win32-x64-msvc': 2.0.0-rc.16 + + '@tauri-apps/plugin-shell@2.0.0-rc.1': + dependencies: + '@tauri-apps/api': 2.0.0-rc.5 + + '@types/eslint@8.56.12': + dependencies: + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.5': {} + + '@types/estree@1.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@22.7.0': + dependencies: + undici-types: 6.19.8 + + '@types/web-bluetooth@0.0.14': {} + + '@types/web-bluetooth@0.0.20': {} + + '@vitejs/plugin-legacy@2.3.1(terser@5.34.0)(vite@5.4.7(@types/node@22.7.0)(terser@5.34.0))': + dependencies: + '@babel/standalone': 7.25.6 + core-js: 3.38.1 + magic-string: 0.26.7 + regenerator-runtime: 0.13.11 + systemjs: 6.15.1 + terser: 5.34.0 + vite: 5.4.7(@types/node@22.7.0)(terser@5.34.0) + + '@vitejs/plugin-vue@5.1.4(vite@5.4.7(@types/node@22.7.0)(terser@5.34.0))(vue@3.5.8(typescript@5.6.2))': + dependencies: + vite: 5.4.7(@types/node@22.7.0)(terser@5.34.0) + vue: 3.5.8(typescript@5.6.2) + + '@volar/language-core@2.4.5': + dependencies: + '@volar/source-map': 2.4.5 + + '@volar/source-map@2.4.5': {} + + '@volar/typescript@2.4.5': + dependencies: + '@volar/language-core': 2.4.5 + path-browserify: 1.0.1 + vscode-uri: 3.0.8 + + '@vue/compiler-core@3.5.8': + dependencies: + '@babel/parser': 7.25.6 + '@vue/shared': 3.5.8 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.8': + dependencies: + '@vue/compiler-core': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/compiler-sfc@3.5.8': + dependencies: + '@babel/parser': 7.25.6 + '@vue/compiler-core': 3.5.8 + '@vue/compiler-dom': 3.5.8 + '@vue/compiler-ssr': 3.5.8 + '@vue/shared': 3.5.8 + estree-walker: 2.0.2 + magic-string: 0.30.11 + postcss: 8.4.47 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.8': + dependencies: + '@vue/compiler-dom': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/language-core@2.1.6(typescript@5.6.2)': + dependencies: + '@volar/language-core': 2.4.5 + '@vue/compiler-dom': 3.5.8 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.8 + computeds: 0.0.1 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.6.2 + + '@vue/reactivity@3.5.8': + dependencies: + '@vue/shared': 3.5.8 + + '@vue/runtime-core@3.5.8': + dependencies: + '@vue/reactivity': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/runtime-dom@3.5.8': + dependencies: + '@vue/reactivity': 3.5.8 + '@vue/runtime-core': 3.5.8 + '@vue/shared': 3.5.8 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.8(vue@3.5.8(typescript@5.6.2))': + dependencies: + '@vue/compiler-ssr': 3.5.8 + '@vue/shared': 3.5.8 + vue: 3.5.8(typescript@5.6.2) + + '@vue/shared@3.5.8': {} + + '@vueuse/core@11.1.0(vue@3.5.8(typescript@5.6.2))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 11.1.0 + '@vueuse/shared': 11.1.0(vue@3.5.8(typescript@5.6.2)) + vue-demi: 0.14.10(vue@3.5.8(typescript@5.6.2)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/core@8.9.4(vue@3.5.8(typescript@5.6.2))': + dependencies: + '@types/web-bluetooth': 0.0.14 + '@vueuse/metadata': 8.9.4 + '@vueuse/shared': 8.9.4(vue@3.5.8(typescript@5.6.2)) + vue-demi: 0.14.10(vue@3.5.8(typescript@5.6.2)) + optionalDependencies: + vue: 3.5.8(typescript@5.6.2) + + '@vueuse/metadata@11.1.0': {} + + '@vueuse/metadata@8.9.4': {} + + '@vueuse/shared@11.1.0(vue@3.5.8(typescript@5.6.2))': + dependencies: + vue-demi: 0.14.10(vue@3.5.8(typescript@5.6.2)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/shared@8.9.4(vue@3.5.8(typescript@5.6.2))': + dependencies: + vue-demi: 0.14.10(vue@3.5.8(typescript@5.6.2)) + optionalDependencies: + vue: 3.5.8(typescript@5.6.2) + + acorn-jsx@5.3.2(acorn@8.12.1): + dependencies: + acorn: 8.12.1 + + acorn@8.12.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + asynckit@0.4.0: {} + + autoprefixer@10.4.20(postcss@8.4.47): + dependencies: + browserslist: 4.24.0 + caniuse-lite: 1.0.30001663 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.0 + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + axios@1.7.7: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.0: + dependencies: + caniuse-lite: 1.0.30001663 + electron-to-chromium: 1.5.28 + node-releases: 2.0.18 + update-browserslist-db: 1.1.0(browserslist@4.24.0) + + buffer-from@1.1.2: {} + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001663: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@2.20.3: {} + + commander@4.1.1: {} + + computeds@0.0.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.7: {} + + core-js@3.38.1: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + de-indent@1.0.2: {} + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.28: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + entities@4.5.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@8.0.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.0.0: {} + + eslint@9.11.1(jiti@1.21.6): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@1.21.6)) + '@eslint-community/regexpp': 4.11.1 + '@eslint/config-array': 0.18.0 + '@eslint/core': 0.6.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.11.1 + '@eslint/plugin-kit': 0.2.0 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.3.0 + '@nodelib/fs.walk': 1.2.8 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.7 + escape-string-regexp: 4.0.0 + eslint-scope: 8.0.2 + eslint-visitor-keys: 4.0.0 + espree: 10.1.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + optionalDependencies: + jiti: 1.21.6 + transitivePeerDependencies: + - supports-color + + espree@10.1.0: + dependencies: + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) + eslint-visitor-keys: 4.0.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + + flatted@3.3.1: {} + + follow-redirects@1.15.9: {} + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + form-data@4.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + fp-ts@2.16.9: {} + + fraction.js@4.3.7: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + + globals@14.0.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inherits@2.0.3: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.6: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kolorist@1.8.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@2.1.0: {} + + lilconfig@3.1.2: {} + + lines-and-columns@1.2.4: {} + + local-pkg@0.5.0: + dependencies: + mlly: 1.7.1 + pkg-types: 1.2.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.17.21: {} + + lodash.merge@4.6.2: {} + + lru-cache@10.4.3: {} + + magic-string@0.26.7: + dependencies: + sourcemap-codec: 1.4.8 + + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + mlly@1.7.1: + dependencies: + acorn: 8.12.1 + pathe: 1.1.2 + pkg-types: 1.2.0 + ufo: 1.5.4 + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.7: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.18: {} + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.0: {} + + package-manager-detector@0.2.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path@0.12.7: + dependencies: + process: 0.11.10 + util: 0.10.4 + + pathe@1.1.2: {} + + picocolors@1.1.0: {} + + picomatch@2.3.1: {} + + pify@2.3.0: {} + + pirates@4.0.6: {} + + pkg-types@1.2.0: + dependencies: + confbox: 0.1.7 + mlly: 1.7.1 + pathe: 1.1.2 + + postcss-import@15.1.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + + postcss-js@4.0.1(postcss@8.4.47): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.47 + + postcss-load-config@4.0.2(postcss@8.4.47): + dependencies: + lilconfig: 3.1.2 + yaml: 2.5.1 + optionalDependencies: + postcss: 8.4.47 + + postcss-nested@6.2.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.47: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.0 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + process@0.11.10: {} + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + regenerator-runtime@0.13.11: {} + + resolve-from@4.0.0: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rollup@2.79.1: + optionalDependencies: + fsevents: 2.3.3 + + rollup@4.22.4: + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.22.4 + '@rollup/rollup-android-arm64': 4.22.4 + '@rollup/rollup-darwin-arm64': 4.22.4 + '@rollup/rollup-darwin-x64': 4.22.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.22.4 + '@rollup/rollup-linux-arm-musleabihf': 4.22.4 + '@rollup/rollup-linux-arm64-gnu': 4.22.4 + '@rollup/rollup-linux-arm64-musl': 4.22.4 + '@rollup/rollup-linux-powerpc64le-gnu': 4.22.4 + '@rollup/rollup-linux-riscv64-gnu': 4.22.4 + '@rollup/rollup-linux-s390x-gnu': 4.22.4 + '@rollup/rollup-linux-x64-gnu': 4.22.4 + '@rollup/rollup-linux-x64-musl': 4.22.4 + '@rollup/rollup-win32-arm64-msvc': 4.22.4 + '@rollup/rollup-win32-ia32-msvc': 4.22.4 + '@rollup/rollup-win32-x64-msvc': 4.22.4 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + semver@7.6.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + sortablejs@1.14.0: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sourcemap-codec@1.4.8: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-json-comments@3.1.1: {} + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + systemjs@6.15.1: {} + + tailwindcss@3.4.13: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.2 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.0 + postcss: 8.4.47 + postcss-import: 15.1.0(postcss@8.4.47) + postcss-js: 4.0.1(postcss@8.4.47) + postcss-load-config: 4.0.2(postcss@8.4.47) + postcss-nested: 6.2.0(postcss@8.4.47) + postcss-selector-parser: 6.1.2 + resolve: 1.22.8 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + terser@5.34.0: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.12.1 + commander: 2.20.3 + source-map-support: 0.5.21 + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinyexec@0.3.0: {} + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-interface-checker@0.1.13: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript@5.6.2: {} + + ufo@1.5.4: {} + + undici-types@6.19.8: {} + + unplugin-icons@0.19.3(@vue/compiler-sfc@3.5.8): + dependencies: + '@antfu/install-pkg': 0.4.1 + '@antfu/utils': 0.7.10 + '@iconify/utils': 2.1.33 + debug: 4.3.7 + kolorist: 1.8.0 + local-pkg: 0.5.0 + unplugin: 1.14.1 + optionalDependencies: + '@vue/compiler-sfc': 3.5.8 + transitivePeerDependencies: + - supports-color + - webpack-sources + + unplugin@1.14.1: + dependencies: + acorn: 8.12.1 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.1.0(browserslist@4.24.0): + dependencies: + browserslist: 4.24.0 + escalade: 3.2.0 + picocolors: 1.1.0 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + util@0.10.4: + dependencies: + inherits: 2.0.3 + + vite-plugin-eslint@1.8.1(eslint@9.11.1(jiti@1.21.6))(vite@5.4.7(@types/node@22.7.0)(terser@5.34.0)): + dependencies: + '@rollup/pluginutils': 4.2.1 + '@types/eslint': 8.56.12 + eslint: 9.11.1(jiti@1.21.6) + rollup: 2.79.1 + vite: 5.4.7(@types/node@22.7.0)(terser@5.34.0) + + vite@5.4.7(@types/node@22.7.0)(terser@5.34.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.47 + rollup: 4.22.4 + optionalDependencies: + '@types/node': 22.7.0 + fsevents: 2.3.3 + terser: 5.34.0 + + vscode-uri@3.0.8: {} + + vue-demi@0.14.10(vue@3.5.8(typescript@5.6.2)): + dependencies: + vue: 3.5.8(typescript@5.6.2) + + vue-promise-modals@0.1.0(typescript@5.6.2): + dependencies: + vue: 3.5.8(typescript@5.6.2) + transitivePeerDependencies: + - typescript + + vue-tsc@2.1.6(typescript@5.6.2): + dependencies: + '@volar/typescript': 2.4.5 + '@vue/language-core': 2.1.6(typescript@5.6.2) + semver: 7.6.3 + typescript: 5.6.2 + + vue@3.5.8(typescript@5.6.2): + dependencies: + '@vue/compiler-dom': 3.5.8 + '@vue/compiler-sfc': 3.5.8 + '@vue/runtime-dom': 3.5.8 + '@vue/server-renderer': 3.5.8(vue@3.5.8(typescript@5.6.2)) + '@vue/shared': 3.5.8 + optionalDependencies: + typescript: 5.6.2 + + vuedraggable-es@4.1.1(vue@3.5.8(typescript@5.6.2)): + dependencies: + sortablejs: 1.14.0 + vue: 3.5.8(typescript@5.6.2) + + webpack-virtual-modules@0.6.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + yaml@2.5.1: {} + + yocto-queue@0.1.0: {} diff --git a/packages/hoppscotch-agent/postcss.config.js b/packages/hoppscotch-agent/postcss.config.js new file mode 100644 index 0000000000..2e7af2b7f1 --- /dev/null +++ b/packages/hoppscotch-agent/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/packages/hoppscotch-agent/public/tauri.svg b/packages/hoppscotch-agent/public/tauri.svg new file mode 100644 index 0000000000..31b62c9280 --- /dev/null +++ b/packages/hoppscotch-agent/public/tauri.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/hoppscotch-agent/public/vite.svg b/packages/hoppscotch-agent/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/packages/hoppscotch-agent/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/hoppscotch-agent/src-tauri/.cargo/config.toml b/packages/hoppscotch-agent/src-tauri/.cargo/config.toml new file mode 100644 index 0000000000..9017258b47 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/.cargo/config.toml @@ -0,0 +1,25 @@ +# Enable static linking for C runtime library on Windows. +# +# Rust uses the msvc toolchain on Windows, +# which by default dynamically links the C runtime (CRT) to the binary. +# +# This creates a runtime dependency on the Visual C++ Redistributable (`vcredist`), +# meaning the target machine must have `vcredist` installed for the application to run. +# +# Since `portable` version doesn't have an installer, +# we can't rely on it to install dependencies, so this config. +# +# Basically: +# - The `+crt-static` flag instructs the Rust compiler to statically link the C runtime for Windows builds.\ +# - To avoids runtime errors related to missing `vcredist` installations. +# - Results in a larger binary size because the runtime is bundled directly into the executable. +# +# For MSVC targets specifically, it will compile code with `/MT` or static linkage. +# See: - RFC 1721: https://rust-lang.github.io/rfcs/1721-crt-static.html +# - Rust Reference - Runtime: https://doc.rust-lang.org/reference/runtime.html +# - MSVC Linking Options: https://docs.microsoft.com/en-us/cpp/build/reference/md-mt-ld-use-run-time-library +# - Rust Issue #37406: https://github.com/rust-lang/rust/issues/37406 +# - Tauri Issue #3048: https://github.com/tauri-apps/tauri/issues/3048 +# - Rust Linkage: https://doc.rust-lang.org/reference/linkage.html +[target.'cfg(windows)'] +rustflags = ["-C", "target-feature=+crt-static"] diff --git a/packages/hoppscotch-agent/src-tauri/.gitignore b/packages/hoppscotch-agent/src-tauri/.gitignore new file mode 100644 index 0000000000..b21bd681d9 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/packages/hoppscotch-agent/src-tauri/Cargo.lock b/packages/hoppscotch-agent/src-tauri/Cargo.lock new file mode 100644 index 0000000000..4893f4e862 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/Cargo.lock @@ -0,0 +1,6482 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "ashpd" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d43c03d9e36dd40cab48435be0b09646da362c278223ca535493877b2c1dee9" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.8.5", + "raw-window-handle 0.6.2", + "serde", + "serde_repr", + "tokio", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-broadcast" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb8f1d480b0ea3783ab015936d2a55c87e219676f0c0b7dec61494043f21857" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "atk" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4af014b17dd80e8af9fa689b2d4a211ddba6eb583c1622f35d0cb543f6b17e4" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "auto-launch" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f012b8cc0c850f34117ec8252a44418f2e34a2cf501de89e29b241ae5f79471" +dependencies = [ + "dirs 4.0.0", + "thiserror", + "winreg 0.10.1", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axum" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa 1.0.11", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.1", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-extra" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c3220b188aea709cf1b6c5f9b01c3bd936bb08bd2b5184a12b35ac8131b1f9" +dependencies = [ + "axum", + "axum-core", + "bytes", + "futures-util", + "headers", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "serde", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base16" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.6.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cargo_toml" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719" +dependencies = [ + "serde", + "toml 0.8.2", +] + +[[package]] +name = "cc" +version = "1.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation 0.1.2", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types 0.5.0", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +dependencies = [ + "bitflags 2.6.0", + "block", + "cocoa-foundation 0.2.0", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "foreign-types 0.5.0", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" +dependencies = [ + "bitflags 2.6.0", + "block", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "libc", + "objc", +] + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4934e6b7e8419148b6ef56950d277af8561060b56afd59e2aadf98b59fce6baa" +dependencies = [ + "cookie", + "idna 0.5.0", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.85", +] + +[[package]] +name = "ctor" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +dependencies = [ + "quote", + "syn 2.0.85", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curl" +version = "0.4.47" +source = "git+https://github.com/CuriousCorrelation/curl-rust.git#1ec8079cf527b9cf47cc7a48c68b458affdae273" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "socket2", +] + +[[package]] +name = "curl-sys" +version = "0.4.77+curl-8.10.1" +source = "git+https://github.com/CuriousCorrelation/curl-rust.git#1ec8079cf527b9cf47cc7a48c68b458affdae273" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "windows-sys 0.52.0", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.85", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "serde", +] + +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.85", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.5", +] + +[[package]] +name = "dlopen2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "embed-resource" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e24052d7be71f0efb50c201557f6fe7d237cfd5a64fd5bcd7fd8fe32dbbffa" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.8.2", + "vswhom", + "winreg 0.52.0", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fdeflate" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "flate2" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5ba081bdef3b75ebcdbfc953699ed2d7417d6bd853347a42a37d76406a33646" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90fbf5c033c65d93792192a49a8efb5bb1e640c419682a58bb96f5ae77f3d4a" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2ea8a4909d530f79921290389cbd7c34cb9d623bfe970eaae65ca5f9cd9cce" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee8f00f4ee46cad2939b8990f5c70c94ff882c3028f3cc5abf950fa4ab53043" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.6.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93c4f5e0e20b60e10631a5f06da7fe3dda744b05ad0ea71fee2f47adf865890c" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771437bf1de2c1c0b496c11505bdf748e26066bbe942dfc8f614c9460f6d7722" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6063efb63db582968fb7df72e1ae68aa6360dcfb0a75143f34fc7d616bad75e" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "hoppscotch-agent" +version = "0.1.3" +dependencies = [ + "aes-gcm", + "axum", + "axum-extra", + "base16", + "chrono", + "dashmap", + "env_logger", + "hoppscotch-relay", + "lazy_static", + "log", + "mockito", + "native-dialog", + "rand 0.8.5", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-autostart", + "tauri-plugin-dialog", + "tauri-plugin-http", + "tauri-plugin-shell", + "tauri-plugin-single-instance", + "tauri-plugin-store", + "tauri-plugin-updater", + "tempfile", + "thiserror", + "tokio", + "tokio-util", + "tower-http", + "uuid", + "winreg 0.52.0", + "x25519-dalek", +] + +[[package]] +name = "hoppscotch-relay" +version = "0.1.1" +dependencies = [ + "curl", + "env_logger", + "http", + "log", + "openssl", + "openssl-sys", + "serde", + "serde_json", + "thiserror", + "tokio-util", + "url-escape", +] + +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.11", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.11", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core 0.52.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.0", + "serde", +] + +[[package]] +name = "infer" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc150e5ce2330295b8616ce0e3f53250e53af31759a9dbedad1621ba29151847" +dependencies = [ + "cfb", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc" +dependencies = [ + "jsonptr 0.4.7", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr 0.6.3", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonptr" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627" +dependencies = [ + "fluent-uri", + "serde", + "serde_json", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.6.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 1.9.3", + "matches", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall", +] + +[[package]] +name = "libz-sys" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "minisign-verify" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a05b5d0594e0cb1ad8cee3373018d2b84e25905dc75b2468114cc9a8e86cfc20" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "mockito" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b34bd91b9e5c5b06338d392463e1318d683cf82ec3d3af4014609be6e2108d" +dependencies = [ + "assert-json-diff", + "bytes", + "colored", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.8.5", + "regex", + "serde_json", + "serde_urlencoded", + "similar", + "tokio", +] + +[[package]] +name = "muda" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8123dfd4996055ac9b15a60ad263b44b01e539007523ad7a4a533a3d93b0591" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror", + "windows-sys 0.59.0", +] + +[[package]] +name = "native-dialog" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e7038885d2aeab236bd60da9e159a5967b47cde3292da3b15ff1bec27c039f" +dependencies = [ + "ascii", + "block", + "cocoa 0.25.0", + "core-foundation 0.9.4", + "dirs-next", + "objc", + "objc-foundation", + "objc_id", + "once_cell", + "raw-window-handle 0.5.2", + "thiserror", + "versions", + "wfd", + "which", + "winapi", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.6.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle 0.6.2", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate 2.0.2", + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "open" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "300.4.0+3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "os_pipe" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pathdiff" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros 0.11.2", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "plist" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" +dependencies = [ + "base64 0.22.1", + "indexmap 2.6.0", + "quick-xml 0.32.0", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" +dependencies = [ + "idna 0.3.0", + "psl-types", +] + +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring", + "rustc-hash", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +dependencies = [ + "async-compression", + "base64 0.22.1", + "bytes", + "cookie", + "cookie_store", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "rfd" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8af382a047821a08aa6bfc09ab0d80ff48d45d8726f7cd8e44891f7cb4a4278e" +dependencies = [ + "ashpd", + "block2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "raw-window-handle 0.6.2", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.85", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.213" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2676ba99bd82f75cae5cbd2c8eda6fa0b8760f18978ea840e980dd5567b5c5b6" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + +[[package]] +name = "serde_derive" +version = "1.0.213" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa 1.0.11", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa 1.0.11", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.11", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.6.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shared_child" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics 0.24.0", + "foreign-types 0.5.0", + "js-sys", + "log", + "objc2", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle 0.6.2", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0dbbebe82d02044dfa481adca1550d6dd7bd16e086bc34fa0fbecceb5a63751" +dependencies = [ + "bitflags 2.6.0", + "cocoa 0.26.0", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "instant", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "raw-window-handle 0.6.2", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.58.0", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "tar" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3889b392db6d32a105d3757230ea0220090b8f94c90d3e60b6c5eb91178ab1b" +dependencies = [ + "anyhow", + "bytes", + "dirs 5.0.1", + "dunce", + "embed_plist", + "futures-util", + "getrandom 0.2.15", + "glob", + "gtk", + "heck 0.5.0", + "http", + "image", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "percent-encoding", + "plist", + "raw-window-handle 0.6.2", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror", + "tokio", + "tray-icon", + "url", + "urlpattern", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f96827ccfb1aa40d55d0ded79562d18ba18566657a553f992a982d755148376" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs 5.0.1", + "glob", + "heck 0.5.0", + "json-patch 3.0.1", + "schemars", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.8.2", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947f16f47becd9e9cd39b74ee337fd1981574d78819be18e4384d85e5a0b82f" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch 2.0.0", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.85", + "tauri-utils", + "thiserror", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd1c8d4a66799d3438747c3a79705cd665a95d6f24cb5f315413ff7a981fe2a" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.85", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa4e6c94cb1d635f65a770c69e23de1bc054b0e4c554fa037a7cc7676333d39" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars", + "serde", + "serde_json", + "tauri-utils", + "toml 0.8.2", + "walkdir", +] + +[[package]] +name = "tauri-plugin-autostart" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba6bb936e0fd0a58ed958b49e2e423dd40949c9d9425cc991be996959e3838e" +dependencies = [ + "auto-launch", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4307310e1d2c09ab110235834722e7c2b85099b683e1eb7342ab351b0be5ada3" +dependencies = [ + "log", + "raw-window-handle 0.6.2", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ba7d46e86db8c830d143ef90ab5a453328365b0cc834c24edea4267b16aba0" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror", + "url", + "uuid", +] + +[[package]] +name = "tauri-plugin-http" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c752aee1b00ec3c4d4f440095995d9bd2c640b478f2067d1fba388900b82eb96" +dependencies = [ + "data-url", + "http", + "regex", + "reqwest", + "schemars", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror", + "tokio", + "url", + "urlpattern", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad7880c5586b6b2104be451e3d7fc0f3800c84bda69e9ba81c828f87cb34267" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror", + "tokio", +] + +[[package]] +name = "tauri-plugin-single-instance" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25ac834491d089699a2bc9266a662faf373c9f779f05a2235bc6e4d9e61769a" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "thiserror", + "windows-sys 0.59.0", + "zbus", +] + +[[package]] +name = "tauri-plugin-store" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9a580be53f04bb62422d239aa798e88522877f58a0d4a0e745f030055a51bb4" +dependencies = [ + "dunce", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror", + "tokio", +] + +[[package]] +name = "tauri-plugin-updater" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd3d2fe0f02bf52eebb5a9d23b987fffac6684646ab6fd683d706dafb18da87" +dependencies = [ + "base64 0.22.1", + "dirs 5.0.1", + "flate2", + "futures-util", + "http", + "infer", + "minisign-verify", + "percent-encoding", + "reqwest", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror", + "time", + "tokio", + "url", + "windows-sys 0.59.0", + "zip", +] + +[[package]] +name = "tauri-runtime" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ef7363e7229ac8d04e8a5d405670dbd43dde8fc4bc3bc56105c35452d03784" +dependencies = [ + "dpi", + "gtk", + "http", + "jni", + "raw-window-handle 0.6.2", + "serde", + "serde_json", + "tauri-utils", + "thiserror", + "url", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62fa2068e8498ad007b54d5773d03d57c3ff6dd96f8c8ce58beff44d0d5e0d30" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "percent-encoding", + "raw-window-handle 0.6.2", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc65d6f5c54e56b66258948a6d9e47a82ea41f4b5a7612bfbdd1634c2913ed0" +dependencies = [ + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "infer", + "json-patch 2.0.0", + "kuchikiki", + "log", + "memchr", + "phf 0.11.2", + "proc-macro2", + "quote", + "regex", + "schemars", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror", + "toml 0.8.2", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" +dependencies = [ + "embed-resource", + "toml 0.7.8", +] + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + +[[package]] +name = "thiserror" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa 1.0.11", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.19.15", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92af36a182b46206723bdf8a7942e20838cde1cf062e5b97854d57eb01763b" +dependencies = [ + "core-graphics 0.24.0", + "crossbeam-channel", + "dirs 5.0.1", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror", + "windows-sys 0.59.0", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna 0.5.0", + "percent-encoding", + "serde", +] + +[[package]] +name = "url-escape" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44e0ce4d1246d075ca5abec4b41d33e87a6054d08e2366b63205665e950db218" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom 0.2.15", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "versions" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c73a36bc44e3039f51fbee93e39f41225f6b17b380eb70cc2aab942df06b34dd" +dependencies = [ + "itertools", + "nom", +] + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.85", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "wasm-streams" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +dependencies = [ + "bitflags 2.6.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +dependencies = [ + "proc-macro2", + "quick-xml 0.36.2", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-roots" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61ff3d9d0ee4efcb461b14eb3acfda2702d10dc329f339303fc3e57215ae2c" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.58.0", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "webview2-com-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886" +dependencies = [ + "thiserror", + "windows", + "windows-core 0.58.0", +] + +[[package]] +name = "wfd" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e713040b67aae5bf1a0ae3e1ebba8cc29ab2b90da9aa1bff6e09031a8a41d7a8" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ea403deff7b51fff19e261330f71608ff2cdef5721d72b64180bb95be7c4150" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-foundation", + "raw-window-handle 0.6.2", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-version" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wry" +version = "0.46.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd5cdf57c66813d97601181349c63b96994b3074fc3d7a31a8cce96e968e3bbd" +dependencies = [ + "base64 0.22.1", + "block2", + "crossbeam-channel", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle 0.6.2", + "sha2", + "soup3", + "tao-macros", + "thiserror", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.58.0", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "zbus" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b8e3d6ae3342792a6cc2340e4394334c7402f3d793b390d2c5494a4032b3030" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "derivative", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a3e850ff1e7217a3b7a07eba90d37fe9bb9e89a310f718afcde5885ca9b6d7" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "zip" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "indexmap 2.6.0", + "memchr", + "thiserror", +] + +[[package]] +name = "zvariant" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e09e8be97d44eeab994d752f341e67b3b0d80512a8b315a0671d47232ef1b65" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "url", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a5857e2856435331636a9fbb415b09243df4521a267c5bedcd5289b4d5799e" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00bedb16a193cc12451873fee2a1bc6550225acece0e36f333e68326c73c8172" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/packages/hoppscotch-agent/src-tauri/Cargo.toml b/packages/hoppscotch-agent/src-tauri/Cargo.toml new file mode 100644 index 0000000000..79b8140ac2 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "hoppscotch-agent" +version = "0.1.3" +description = "A cross-platform HTTP request agent for Hoppscotch for advanced request handling including custom headers, certificates, proxies, and local system integration." +authors = ["AndrewBastin", "CuriousCorrelation"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "hoppscotch_agent_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2.0.1", features = [] } + +[dependencies] +tauri = { version = "2.0.4", features = ["tray-icon", "image-png"] } +tauri-plugin-shell = "2.0.1" +tauri-plugin-autostart = { version = "2.0.1", optional = true } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1.40.0", features = ["full"] } +dashmap = { version = "6.1.0", features = ["serde"] } +axum = { version = "0.7.7" } +axum-extra = { version = "0.9.4", features = ["typed-header"] } +tower-http = { version = "0.6.1", features = ["cors"] } +tokio-util = "0.7.12" +uuid = { version = "1.11.0", features = [ "v4", "fast-rng" ] } +chrono = { version = "0.4", features = ["serde"] } +rand = "0.8.5" +log = "0.4.22" +env_logger = "0.11.5" +hoppscotch-relay = { path = "../../hoppscotch-relay" } +thiserror = "1.0.64" +tauri-plugin-store = "2.1.0" +x25519-dalek = { version = "2.0.1", features = ["getrandom"] } +base16 = "0.2.1" +aes-gcm = { version = "0.10.3", features = ["aes"] } +tauri-plugin-updater = "2.0.2" +tauri-plugin-dialog = "2.0.1" +lazy_static = "1.5.0" +tauri-plugin-single-instance = "2.0.1" +tauri-plugin-http = { version = "2.0.1", features = ["gzip"] } +native-dialog = "0.7.0" + +[target.'cfg(windows)'.dependencies] +tempfile = { version = "3.13.0" } +winreg = { version = "0.52.0" } + +[dev-dependencies] +mockito = "1.5.0" + +[features] +default = ["tauri-plugin-autostart"] +portable = [] diff --git a/packages/hoppscotch-agent/src-tauri/build.rs b/packages/hoppscotch-agent/src-tauri/build.rs new file mode 100644 index 0000000000..65c5c67e69 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/build.rs @@ -0,0 +1,5 @@ +fn main() { + tauri_build::build(); + println!("cargo::rerun-if-env-changed=UPDATER_PUB_KEY"); + println!("cargo::rerun-if-env-changed=UPDATER_URL"); +} diff --git a/packages/hoppscotch-agent/src-tauri/capabilities/default.json b/packages/hoppscotch-agent/src-tauri/capabilities/default.json new file mode 100644 index 0000000000..fe0f3775cb --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/capabilities/default.json @@ -0,0 +1,24 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": ["main", "test"], + "permissions": [ + { + "identifier": "http:default", + "allow": [ + { + "url": "https://*.tauri.app" + }, + { + "url": "https://*.microsoft.*" + } + ] + }, + "core:default", + "shell:allow-open", + "core:window:allow-close", + "core:window:allow-set-focus", + "core:window:allow-set-always-on-top" + ] +} diff --git a/packages/hoppscotch-agent/src-tauri/icons/128x128.png b/packages/hoppscotch-agent/src-tauri/icons/128x128.png new file mode 100644 index 0000000000..3778416df4 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/128x128.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/128x128@2x.png b/packages/hoppscotch-agent/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000000..e5b884652e Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/128x128@2x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/32x32.png b/packages/hoppscotch-agent/src-tauri/icons/32x32.png new file mode 100644 index 0000000000..e553d90f75 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/32x32.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/Square107x107Logo.png b/packages/hoppscotch-agent/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000000..e8e31c233a Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/Square107x107Logo.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/Square142x142Logo.png b/packages/hoppscotch-agent/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000000..938bf8dc63 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/Square142x142Logo.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/Square150x150Logo.png b/packages/hoppscotch-agent/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000000..647408c583 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/Square150x150Logo.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/Square284x284Logo.png b/packages/hoppscotch-agent/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000000..a898743cc1 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/Square284x284Logo.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/Square30x30Logo.png b/packages/hoppscotch-agent/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000000..e1a1f772b2 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/Square30x30Logo.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/Square310x310Logo.png b/packages/hoppscotch-agent/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000000..69bf56b185 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/Square310x310Logo.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/Square44x44Logo.png b/packages/hoppscotch-agent/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000000..bb5b895192 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/Square44x44Logo.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/Square71x71Logo.png b/packages/hoppscotch-agent/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000000..68541683f7 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/Square71x71Logo.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/Square89x89Logo.png b/packages/hoppscotch-agent/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000000..94a65e2a40 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/Square89x89Logo.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/StoreLogo.png b/packages/hoppscotch-agent/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000000..db4b61ba56 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/StoreLogo.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..b334e000a9 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..ea31185cdf Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000..b334e000a9 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ee4bfd7daf Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..794a5c62da Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000..ee4bfd7daf Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..fbef3290a9 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..9aee0bf90d Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..fbef3290a9 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..d63d25bcb4 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..1443a0c155 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..d63d25bcb4 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..3e0f1c3987 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000..5f449d412f Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..3e0f1c3987 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/icon.icns b/packages/hoppscotch-agent/src-tauri/icons/icon.icns new file mode 100644 index 0000000000..293556d77f Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/icon.icns differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/icon.ico b/packages/hoppscotch-agent/src-tauri/icons/icon.ico new file mode 100644 index 0000000000..3e3249c9d6 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/icon.ico differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/icon.png b/packages/hoppscotch-agent/src-tauri/icons/icon.png new file mode 100644 index 0000000000..a91230bbce Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/icon.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@1x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000000..e63a928c15 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000000..8caffb7b04 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@2x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000000..8caffb7b04 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@3x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000000..c51270a051 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@1x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000000..c89515b2fe Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000000..4c23c68780 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@2x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000000..4c23c68780 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@3x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000000..cd117af20f Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@1x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000000..8caffb7b04 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000000..9f5c51d781 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@2x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000000..9f5c51d781 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@3x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000000..97738c12db Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-512@2x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000000..ed29af6821 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-60x60@2x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000000..97738c12db Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-60x60@3x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000000..6eb8333648 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-76x76@1x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000000..38d7d795d4 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-76x76@2x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000000..4dbf0d7920 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000000..aed3613329 Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/packages/hoppscotch-agent/src-tauri/icons/tray_icon.png b/packages/hoppscotch-agent/src-tauri/icons/tray_icon.png new file mode 100644 index 0000000000..b29857f47e Binary files /dev/null and b/packages/hoppscotch-agent/src-tauri/icons/tray_icon.png differ diff --git a/packages/hoppscotch-agent/src-tauri/src/controller.rs b/packages/hoppscotch-agent/src-tauri/src/controller.rs new file mode 100644 index 0000000000..00e0006f93 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/controller.rs @@ -0,0 +1,220 @@ +use axum::{ + body::Bytes, + extract::{Path, State}, + http::HeaderMap, + Json, +}; +use axum_extra::{ + headers::{authorization::Bearer, Authorization}, + TypedHeader, +}; +use hoppscotch_relay::{RequestWithMetadata, ResponseWithMetadata}; +use std::sync::Arc; +use tauri::{AppHandle, Emitter}; +use x25519_dalek::{EphemeralSecret, PublicKey}; + +use crate::{ + error::{AgentError, AgentResult}, + model::{AuthKeyResponse, ConfirmedRegistrationRequest, HandshakeResponse}, + state::{AppState, Registration}, + util::EncryptedJson, +}; +use chrono::Utc; +use rand::Rng; +use serde_json::json; +use uuid::Uuid; + +fn generate_otp() -> String { + let otp: u32 = rand::thread_rng().gen_range(0..1_000_000); + + format!("{:06}", otp) +} + +pub async fn handshake( + State((_, app_handle)): State<(Arc, AppHandle)>, +) -> AgentResult> { + Ok(Json(HandshakeResponse { + status: "success".to_string(), + __hoppscotch__agent__: true, + agent_version: app_handle.package_info().version.to_string(), + })) +} + +pub async fn receive_registration( + State((state, app_handle)): State<(Arc, AppHandle)>, +) -> AgentResult> { + let otp = generate_otp(); + + let mut active_registration_code = state.active_registration_code.write().await; + + if !active_registration_code.is_none() { + return Ok(Json( + json!({ "message": "There is already an existing registration happening" }), + )); + } + + *active_registration_code = Some(otp.clone()); + + app_handle + .emit("registration_received", otp) + .map_err(|_| AgentError::InternalServerError)?; + + Ok(Json( + json!({ "message": "Registration received and stored" }), + )) +} + +pub async fn verify_registration( + State((state, app_handle)): State<(Arc, AppHandle)>, + Json(confirmed_registration): Json, +) -> AgentResult> { + state + .validate_registration(&confirmed_registration.registration) + .await + .then_some(()) + .ok_or(AgentError::InvalidRegistration)?; + + let auth_key = Uuid::new_v4().to_string(); + let created_at = Utc::now(); + + let auth_key_copy = auth_key.clone(); + + let agent_secret_key = EphemeralSecret::random(); + let agent_public_key = PublicKey::from(&agent_secret_key); + + let their_public_key = { + let public_key_slice: &[u8; 32] = + &base16::decode(&confirmed_registration.client_public_key_b16) + .map_err(|_| AgentError::InvalidClientPublicKey)?[0..32] + .try_into() + .map_err(|_| AgentError::InvalidClientPublicKey)?; + + PublicKey::from(public_key_slice.to_owned()) + }; + + let shared_secret = agent_secret_key.diffie_hellman(&their_public_key); + + let _ = state.update_registrations(app_handle.clone(), |regs| { + regs.insert( + auth_key_copy, + Registration { + registered_at: created_at, + shared_secret_b16: base16::encode_lower(shared_secret.as_bytes()), + }, + ); + })?; + + let auth_payload = json!({ + "auth_key": auth_key, + "created_at": created_at + }); + + app_handle + .emit("authenticated", &auth_payload) + .map_err(|_| AgentError::InternalServerError)?; + + Ok(Json(AuthKeyResponse { + auth_key, + created_at, + agent_public_key_b16: base16::encode_lower(agent_public_key.as_bytes()), + })) +} + +pub async fn run_request( + State((state, _app_handle)): State<(Arc, T)>, + TypedHeader(auth_header): TypedHeader>, + headers: HeaderMap, + body: Bytes, +) -> AgentResult> { + let nonce = headers + .get("X-Hopp-Nonce") + .ok_or(AgentError::Unauthorized)? + .to_str() + .map_err(|_| AgentError::Unauthorized)?; + + let req: RequestWithMetadata = state + .validate_access_and_get_data(auth_header.token(), nonce, &body) + .ok_or(AgentError::Unauthorized)?; + + let req_id = req.req_id; + + let reg_info = state + .get_registration_info(auth_header.token()) + .ok_or(AgentError::Unauthorized)?; + + let cancel_token = tokio_util::sync::CancellationToken::new(); + state.add_cancellation_token(req.req_id, cancel_token.clone()); + + let cancel_token_clone = cancel_token.clone(); + // Execute the HTTP request in a blocking thread pool and handles cancellation. + // + // It: + // 1. Uses `spawn_blocking` to run the sync `run_request_task` + // without blocking the main Tokio runtime. + // 2. Uses `select!` to concurrently wait for either + // a. the task to complete, + // b. or a cancellation signal. + // + // Why spawn_blocking? + // - `run_request_task` uses synchronous curl operations which would block + // the async runtime if not run in a separate thread. + // - `spawn_blocking` moves this operation to a thread pool designed for + // blocking tasks, so other async operations to continue unblocked. + let result = tokio::select! { + res = tokio::task::spawn_blocking(move || hoppscotch_relay::run_request_task(&req, cancel_token_clone)) => { + match res { + Ok(task_result) => Ok(task_result?), + Err(_) => Err(AgentError::InternalServerError), + } + }, + _ = cancel_token.cancelled() => { + Err(AgentError::RequestCancelled) + } + }; + + state.remove_cancellation_token(req_id); + + result.map(|val| EncryptedJson { + key_b16: reg_info.shared_secret_b16, + data: val, + }) +} + +/// Provides a way for registered clients to check if their +/// registration still holds, this route is supposed to return +/// an encrypted `true` value if the given auth_key is good. +/// Since its encrypted with the shared secret established during +/// registration, the client also needs the shared secret to verify +/// if the read fails, or the auth_key didn't validate and this route returns +/// undefined, we can count on the registration not being valid anymore. +pub async fn registered_handshake( + State((state, _)): State<(Arc, AppHandle)>, + TypedHeader(auth_header): TypedHeader>, +) -> AgentResult> { + let reg_info = state.get_registration_info(auth_header.token()); + + match reg_info { + Some(reg) => Ok(EncryptedJson { + key_b16: reg.shared_secret_b16, + data: json!(true), + }), + None => Err(AgentError::Unauthorized), + } +} + +pub async fn cancel_request( + State((state, _app_handle)): State<(Arc, T)>, + TypedHeader(auth_header): TypedHeader>, + Path(req_id): Path, +) -> AgentResult> { + if !state.validate_access(auth_header.token()) { + return Err(AgentError::Unauthorized); + } + + if let Some((_, token)) = state.remove_cancellation_token(req_id) { + token.cancel(); + Ok(Json(json!({"message": "Request cancelled successfully"}))) + } else { + Err(AgentError::RequestNotFound) + } +} diff --git a/packages/hoppscotch-agent/src-tauri/src/dialog.rs b/packages/hoppscotch-agent/src-tauri/src/dialog.rs new file mode 100644 index 0000000000..5b8a19ecce --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/dialog.rs @@ -0,0 +1,58 @@ +use native_dialog::{MessageDialog, MessageType}; + +pub fn panic(msg: &str) { + const FATAL_ERROR: &str = "Fatal error"; + + MessageDialog::new() + .set_type(MessageType::Error) + .set_title(FATAL_ERROR) + .set_text(msg) + .show_alert() + .unwrap_or_default(); + + log::error!("{}: {}", FATAL_ERROR, msg); + + panic!("{}: {}", FATAL_ERROR, msg); +} + +pub fn info(msg: &str) { + log::info!("{}", msg); + + MessageDialog::new() + .set_type(MessageType::Info) + .set_title("Info") + .set_text(msg) + .show_alert() + .unwrap_or_default(); +} + +pub fn warn(msg: &str) { + log::warn!("{}", msg); + + MessageDialog::new() + .set_type(MessageType::Warning) + .set_title("Warning") + .set_text(msg) + .show_alert() + .unwrap_or_default(); +} + +pub fn error(msg: &str) { + log::error!("{}", msg); + + MessageDialog::new() + .set_type(MessageType::Error) + .set_title("Error") + .set_text(msg) + .show_alert() + .unwrap_or_default(); +} + +pub fn confirm(title: &str, msg: &str, icon: MessageType) -> bool { + MessageDialog::new() + .set_type(icon) + .set_title(title) + .set_text(msg) + .show_confirm() + .unwrap_or_default() +} diff --git a/packages/hoppscotch-agent/src-tauri/src/error.rs b/packages/hoppscotch-agent/src-tauri/src/error.rs new file mode 100644 index 0000000000..697b3dd3b0 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/error.rs @@ -0,0 +1,81 @@ +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, + Json, +}; +use serde_json::json; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum AgentError { + #[error("Invalid Registration")] + InvalidRegistration, + #[error("Invalid Client Public Key")] + InvalidClientPublicKey, + #[error("Unauthorized")] + Unauthorized, + #[error("Request not found or already completed")] + RequestNotFound, + #[error("Internal server error")] + InternalServerError, + #[error("Invalid request: {0}")] + BadRequest(String), + #[error("Client certificate error")] + ClientCertError, + #[error("Root certificate error")] + RootCertError, + #[error("Invalid method")] + InvalidMethod, + #[error("Invalid URL")] + InvalidUrl, + #[error("Invalid headers")] + InvalidHeaders, + #[error("Request run error: {0}")] + RequestRunError(String), + #[error("Request cancelled")] + RequestCancelled, + #[error("Failed to clear registrations")] + RegistrationClearError, + #[error("Failed to insert registrations")] + RegistrationInsertError, + #[error("Failed to save registrations to store")] + RegistrationSaveError, + #[error("Serde error: {0}")] + Serde(#[from] serde_json::Error), + #[error("Store error: {0}")] + TauriPluginStore(#[from] tauri_plugin_store::Error), + #[error("Relay error: {0}")] + Relay(#[from] hoppscotch_relay::RelayError), +} + +impl IntoResponse for AgentError { + fn into_response(self) -> Response { + let (status, error_message) = match self { + AgentError::InvalidRegistration => (StatusCode::BAD_REQUEST, self.to_string()), + AgentError::InvalidClientPublicKey => (StatusCode::BAD_REQUEST, self.to_string()), + AgentError::Unauthorized => (StatusCode::UNAUTHORIZED, self.to_string()), + AgentError::RequestNotFound => (StatusCode::NOT_FOUND, self.to_string()), + AgentError::InternalServerError => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()), + AgentError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg), + AgentError::ClientCertError => (StatusCode::BAD_REQUEST, self.to_string()), + AgentError::RootCertError => (StatusCode::BAD_REQUEST, self.to_string()), + AgentError::InvalidMethod => (StatusCode::BAD_REQUEST, self.to_string()), + AgentError::InvalidUrl => (StatusCode::BAD_REQUEST, self.to_string()), + AgentError::InvalidHeaders => (StatusCode::BAD_REQUEST, self.to_string()), + AgentError::RequestRunError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg), + AgentError::RequestCancelled => (StatusCode::BAD_REQUEST, self.to_string()), + _ => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Internal Server Error".to_string(), + ), + }; + + let body = Json(json!({ + "error": error_message, + })); + + (status, body).into_response() + } +} + +pub type AgentResult = std::result::Result; diff --git a/packages/hoppscotch-agent/src-tauri/src/global.rs b/packages/hoppscotch-agent/src-tauri/src/global.rs new file mode 100644 index 0000000000..feaf7fe857 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/global.rs @@ -0,0 +1,2 @@ +pub const AGENT_STORE: &str = "app_data.bin"; +pub const REGISTRATIONS: &str = "registrations"; diff --git a/packages/hoppscotch-agent/src-tauri/src/lib.rs b/packages/hoppscotch-agent/src-tauri/src/lib.rs new file mode 100644 index 0000000000..b8312eb0dd --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/lib.rs @@ -0,0 +1,178 @@ +pub mod controller; +pub mod dialog; +pub mod error; +pub mod global; +pub mod model; +pub mod route; +pub mod server; +pub mod state; +pub mod tray; +pub mod updater; +pub mod util; +pub mod webview; + +use log::{error, info}; +use std::sync::Arc; +use tauri::{Emitter, Listener, Manager, WebviewWindowBuilder}; +use tauri_plugin_updater::UpdaterExt; +use tokio_util::sync::CancellationToken; + +use model::Payload; +use state::AppState; + +#[tauri::command] +async fn get_otp(state: tauri::State<'_, Arc>) -> Result, ()> { + Ok(state.active_registration_code.read().await.clone()) +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + env_logger::init(); + + // The installer takes care of installing `WebView`, + // this check is only required for portable variant. + #[cfg(all(feature = "portable", windows))] + webview::init_webview(); + + let cancellation_token = CancellationToken::new(); + let server_cancellation_token = cancellation_token.clone(); + + tauri::Builder::default() + // NOTE: Currently, plugins run in the order they were added in to the builder, + // so `tauri_plugin_single_instance` needs to be registered first. + // See: https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/single-instance + .plugin(tauri_plugin_single_instance::init(|app, args, cwd| { + info!("{}, {args:?}, {cwd}", app.package_info().name); + + app.emit("single-instance", Payload::new(args, cwd)) + .unwrap(); + + // Application is already running, bring it to foreground. + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.set_focus(); + } else { + error!("Failed to get `main` window"); + } + })) + .plugin(tauri_plugin_store::Builder::new().build()) + .setup(move |app| { + let app_handle = app.app_handle(); + + #[cfg(all(desktop, not(feature = "portable")))] + { + use tauri_plugin_autostart::MacosLauncher; + use tauri_plugin_autostart::ManagerExt; + + let _ = app.handle().plugin(tauri_plugin_autostart::init( + MacosLauncher::LaunchAgent, + None, + )); + + let autostart_manager = app.autolaunch(); + + println!( + "autostart enabled: {}", + autostart_manager.is_enabled().unwrap() + ); + + if !autostart_manager.is_enabled().unwrap() { + let _ = autostart_manager.enable(); + println!( + "autostart updated: {}", + autostart_manager.is_enabled().unwrap() + ); + } + }; + + #[cfg(desktop)] + { + let _ = app + .handle() + .plugin(tauri_plugin_updater::Builder::new().build()); + + let _ = app.handle().plugin(tauri_plugin_dialog::init()); + + let updater = app.updater_builder().build().unwrap(); + + let app_handle_ref = app_handle.clone(); + + tauri::async_runtime::spawn_blocking(|| { + tauri::async_runtime::block_on(async { + updater::check_and_install_updates(app_handle_ref, updater).await; + }) + }); + }; + + let app_state = Arc::new(AppState::new(app_handle.clone())?); + + app.manage(app_state.clone()); + + let server_cancellation_token = server_cancellation_token.clone(); + + let server_app_handle = app_handle.clone(); + + tauri::async_runtime::spawn(async move { + server::run_server(app_state, server_cancellation_token, server_app_handle).await; + }); + + #[cfg(all(desktop))] + { + let handle = app.handle(); + tray::create_tray(handle)?; + } + + // Blocks the app from populating the macOS dock + #[cfg(target_os = "macos")] + { + app_handle + .set_activation_policy(tauri::ActivationPolicy::Accessory) + .unwrap(); + }; + + let app_handle_ref = app_handle.clone(); + + app_handle.listen("registration_received", move |_| { + WebviewWindowBuilder::from_config( + &app_handle_ref, + &app_handle_ref.config().app.windows[0], + ) + .unwrap() + .build() + .unwrap() + .show() + .unwrap(); + }); + + Ok(()) + }) + .manage(cancellation_token) + .on_window_event(|window, event| { + match &event { + tauri::WindowEvent::CloseRequested { .. } => { + let app_state = window.state::>(); + + let mut current_code = app_state.active_registration_code.blocking_write(); + + if current_code.is_some() { + *current_code = None; + } + } + _ => {} + }; + }) + .invoke_handler(tauri::generate_handler![get_otp]) + .build(tauri::generate_context!()) + .expect("error while building tauri application") + .run(|app_handle, event| match event { + tauri::RunEvent::ExitRequested { api, code, .. } => { + if code.is_none() || matches!(code, Some(0)) { + api.prevent_exit() + } else if code.is_some() { + let state = app_handle.state::(); + state.cancel(); + } + } + _ => {} + }); +} diff --git a/packages/hoppscotch-agent/src-tauri/src/main.rs b/packages/hoppscotch-agent/src-tauri/src/main.rs new file mode 100644 index 0000000000..bf757fbd5a --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + hoppscotch_agent_lib::run() +} diff --git a/packages/hoppscotch-agent/src-tauri/src/model.rs b/packages/hoppscotch-agent/src-tauri/src/model.rs new file mode 100644 index 0000000000..68589fe931 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/model.rs @@ -0,0 +1,47 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// Single instance payload. +#[derive(Clone, Serialize)] +pub struct Payload { + args: Vec, + cwd: String, +} + +impl Payload { + pub fn new(args: Vec, cwd: String) -> Self { + Self { args, cwd } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct HandshakeResponse { + #[allow(non_snake_case)] + pub __hoppscotch__agent__: bool, + + pub status: String, + pub agent_version: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ConfirmedRegistrationRequest { + pub registration: String, + + /// base16 (lowercase) encoded public key shared by the client + /// to the agent so that the agent can establish a shared secret + /// which will be used to encrypt traffic between agent + /// and client after registration + pub client_public_key_b16: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AuthKeyResponse { + pub auth_key: String, + pub created_at: DateTime, + + /// base16 (lowercase) encoded public key shared by the + /// agent so that the client can establish a shared secret + /// which will be used to encrypt traffic between agent + /// and client after registration + pub agent_public_key_b16: String, +} diff --git a/packages/hoppscotch-agent/src-tauri/src/route.rs b/packages/hoppscotch-agent/src-tauri/src/route.rs new file mode 100644 index 0000000000..2906681297 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/route.rs @@ -0,0 +1,28 @@ +use axum::{ + routing::{get, post}, + Router, +}; +use std::sync::Arc; +use tauri::AppHandle; + +use crate::{controller, state::AppState}; + +pub fn route(state: Arc, app_handle: AppHandle) -> Router { + Router::new() + .route("/handshake", get(controller::handshake)) + .route( + "/receive-registration", + post(controller::receive_registration), + ) + .route( + "/verify-registration", + post(controller::verify_registration), + ) + .route( + "/registered-handshake", + get(controller::registered_handshake), + ) + .route("/request", post(controller::run_request)) + .route("/cancel-request/:req_id", post(controller::cancel_request)) + .with_state((state, app_handle)) +} diff --git a/packages/hoppscotch-agent/src-tauri/src/server.rs b/packages/hoppscotch-agent/src-tauri/src/server.rs new file mode 100644 index 0000000000..1e9210faec --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/server.rs @@ -0,0 +1,34 @@ +use axum::Router; +use std::sync::Arc; +use tokio_util::sync::CancellationToken; +use tower_http::cors::CorsLayer; + +use crate::route; +use crate::state::AppState; + +pub async fn run_server( + state: Arc, + cancellation_token: CancellationToken, + app_handle: tauri::AppHandle, +) { + let cors = CorsLayer::permissive(); + + let app = Router::new() + .merge(route::route(state, app_handle)) + .layer(cors); + + let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 9119)); + + println!("Server running on http://{}", addr); + + let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); + + axum::serve(listener, app.into_make_service()) + .with_graceful_shutdown(async move { + cancellation_token.cancelled().await; + }) + .await + .unwrap(); + + println!("Server shut down"); +} diff --git a/packages/hoppscotch-agent/src-tauri/src/state.rs b/packages/hoppscotch-agent/src-tauri/src/state.rs new file mode 100644 index 0000000000..c8e8c928b0 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/state.rs @@ -0,0 +1,161 @@ +use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; +use axum::body::Bytes; +use chrono::{DateTime, Utc}; +use dashmap::DashMap; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use tauri_plugin_store::StoreExt; +use tokio::sync::RwLock; +use tokio_util::sync::CancellationToken; + +use crate::{ + error::{AgentError, AgentResult}, + global::{AGENT_STORE, REGISTRATIONS}, +}; + +/// Describes one registered app instance +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Registration { + pub registered_at: DateTime, + + /// base16 (lowercase) encoded shared secret that the client + /// and agent established during registration that is used + /// to encrypt traffic between them + pub shared_secret_b16: String, +} + +#[derive(Default)] +pub struct AppState { + /// The active registration code that is being registered. + pub active_registration_code: RwLock>, + + /// Cancellation Tokens for the running requests + pub cancellation_tokens: DashMap, + + /// Registrations against the agent, the key is the auth + /// token associated to the registration + registrations: DashMap, +} + +impl AppState { + pub fn new(app_handle: tauri::AppHandle) -> AgentResult { + let store = app_handle.store(AGENT_STORE)?; + + // Try loading and parsing registrations from the store, if that failed, + // load the default list + let registrations = store + .get(REGISTRATIONS) + .and_then(|val| serde_json::from_value(val.clone()).ok()) + .unwrap_or_else(|| DashMap::new()); + + // Try to save the latest registrations list + let _ = store.set(REGISTRATIONS, serde_json::to_value(®istrations)?); + let _ = store.save(); + + Ok(Self { + active_registration_code: RwLock::new(None), + cancellation_tokens: DashMap::new(), + registrations, + }) + } + + /// Gets you a readonly reference to the registrations list + /// NOTE: Although DashMap API allows you to update the list from an immutable + /// reference, you shouldn't do it for registrations as `update_registrations` + /// performs save operation that needs to be done and should be used instead + pub fn get_registrations(&self) -> &DashMap { + &self.registrations + } + + /// Provides you an opportunity to update the registrations list + /// and also persists the data to the disk. + /// This function bypasses `store.reload()` to avoid issues from stale or inconsistent + /// data on disk. By relying solely on the in-memory `self.registrations`, + /// we make sure that updates are applied based on the most recent changes in memory. + pub fn update_registrations( + &self, + app_handle: tauri::AppHandle, + update_func: impl FnOnce(&DashMap), + ) -> Result<(), AgentError> { + update_func(&self.registrations); + + let store = app_handle.store(AGENT_STORE)?; + + if store.has(REGISTRATIONS) { + // We've confirmed `REGISTRATIONS` exists in the store + store + .delete(REGISTRATIONS) + .then_some(()) + .ok_or(AgentError::RegistrationClearError)?; + } else { + log::debug!("`REGISTRATIONS` key not found in store; continuing with update."); + } + + // Since we've established `self.registrations` as the source of truth, + // we avoid reloading the store from disk and instead choose to override it. + + store.set( + REGISTRATIONS, + serde_json::to_value(self.registrations.clone())?, + ); + + // Explicitly save the changes + store.save()?; + + Ok(()) + } + + /// Clear all the registrations + pub fn clear_registrations(&self, app_handle: tauri::AppHandle) -> Result<(), AgentError> { + Ok(self.update_registrations(app_handle, |registrations| registrations.clear())?) + } + + pub async fn validate_registration(&self, registration: &str) -> bool { + self.active_registration_code.read().await.as_deref() == Some(registration) + } + + pub fn remove_cancellation_token(&self, req_id: usize) -> Option<(usize, CancellationToken)> { + self.cancellation_tokens.remove(&req_id) + } + + pub fn add_cancellation_token(&self, req_id: usize, cancellation_tokens: CancellationToken) { + self.cancellation_tokens.insert(req_id, cancellation_tokens); + } + + pub fn validate_access(&self, auth_key: &str) -> bool { + self.registrations.get(auth_key).is_some() + } + + pub fn validate_access_and_get_data( + &self, + auth_key: &str, + nonce: &str, + data: &Bytes, + ) -> Option + where + T: DeserializeOwned, + { + if let Some(registration) = self.registrations.get(auth_key) { + let key: [u8; 32] = base16::decode(®istration.shared_secret_b16).ok()?[0..32] + .try_into() + .ok()?; + + let nonce: [u8; 12] = base16::decode(nonce).ok()?[0..12].try_into().ok()?; + + let cipher = Aes256Gcm::new(&key.into()); + + let data = data.iter().cloned().collect::>(); + + let plain_data = cipher.decrypt(&nonce.into(), data.as_slice()).ok()?; + + serde_json::from_reader(plain_data.as_slice()).ok() + } else { + None + } + } + + pub fn get_registration_info(&self, auth_key: &str) -> Option { + self.registrations + .get(auth_key) + .map(|reference| reference.value().clone()) + } +} diff --git a/packages/hoppscotch-agent/src-tauri/src/tray.rs b/packages/hoppscotch-agent/src-tauri/src/tray.rs new file mode 100644 index 0000000000..1b1f62e813 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/tray.rs @@ -0,0 +1,91 @@ +use crate::state::AppState; +use lazy_static::lazy_static; +use std::sync::Arc; +use tauri::{ + image::Image, + menu::{MenuBuilder, MenuItem}, + tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, + AppHandle, Manager, +}; + +const TRAY_ICON_DATA: &'static [u8] = include_bytes!("../icons/tray_icon.png"); + +lazy_static! { + static ref TRAY_ICON: Image<'static> = Image::from_bytes(TRAY_ICON_DATA).unwrap(); +} + +pub fn create_tray(app: &AppHandle) -> tauri::Result<()> { + let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?; + let clear_registrations = MenuItem::with_id( + app, + "clear_registrations", + "Clear Registrations", + true, + None::<&str>, + )?; + + let pkg_info = app.package_info(); + let app_name = pkg_info.name.clone(); + let app_version = pkg_info.version.clone(); + + let app_name_item = MenuItem::with_id(app, "app_name", app_name, false, None::<&str>)?; + let app_version_item = MenuItem::with_id( + app, + "app_version", + format!("Version: {}", app_version), + false, + None::<&str>, + )?; + + let menu = MenuBuilder::new(app) + .item(&app_name_item) + .item(&app_version_item) + .separator() + .item(&clear_registrations) + .item(&quit_i) + .build()?; + + let _ = TrayIconBuilder::with_id("hopp-tray") + .tooltip("Hoppscotch Agent") + .icon(if cfg!(target_os = "macos") { + TRAY_ICON.clone() + } else { + app.default_window_icon().unwrap().clone() + }) + .icon_as_template(cfg!(target_os = "macos")) + .menu(&menu) + .menu_on_left_click(true) + .on_menu_event(move |app, event| match event.id.as_ref() { + "quit" => { + log::info!("Exiting the agent..."); + app.exit(-1); + } + "clear_registrations" => { + let app_state = app.state::>(); + + app_state + .clear_registrations(app.clone()) + .expect("Invariant violation: Failed to clear registrations"); + } + _ => { + log::warn!("Unhandled menu event: {:?}", event.id); + } + }) + .on_tray_icon_event(|tray, event| { + if let TrayIconEvent::Click { + button: MouseButton::Left, + button_state: MouseButtonState::Up, + .. + } = event + { + let app = tray.app_handle(); + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.set_focus(); + } + } + }) + .build(app); + + Ok(()) +} diff --git a/packages/hoppscotch-agent/src-tauri/src/updater.rs b/packages/hoppscotch-agent/src-tauri/src/updater.rs new file mode 100644 index 0000000000..490e1f7aa0 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/updater.rs @@ -0,0 +1,67 @@ +use tauri::Manager; +use tauri_plugin_dialog::DialogExt; +use tauri_plugin_dialog::MessageDialogButtons; +use tauri_plugin_dialog::MessageDialogKind; + +#[cfg(feature = "portable")] +use {crate::dialog, crate::util, native_dialog::MessageType}; + +pub async fn check_and_install_updates( + app: tauri::AppHandle, + updater: tauri_plugin_updater::Updater, +) { + let update = updater.check().await; + + if let Ok(Some(update)) = update { + #[cfg(not(feature = "portable"))] + { + let do_update = app + .dialog() + .message(format!( + "Update to {} is available!{}", + update.version, + update + .body + .clone() + .map(|body| format!("\n\nRelease Notes: {}", body)) + .unwrap_or("".into()) + )) + .title("Update Available") + .kind(MessageDialogKind::Info) + .buttons(MessageDialogButtons::OkCancelCustom( + "Update".to_string(), + "Cancel".to_string(), + )) + .blocking_show(); + + if do_update { + let _ = update.download_and_install(|_, _| {}, || {}).await; + + tauri::process::restart(&app.env()); + } + } + #[cfg(feature = "portable")] + { + let download_url = "https://hoppscotch.com/download"; + let message = format!( + "An update (version {}) is available for the Hoppscotch Agent.\n\nPlease download the latest portable version from our website.", + update.version + ); + + dialog::info(&message); + + if dialog::confirm( + "Open Download Page", + "Would you like to open the download page in your browser?", + MessageType::Info, + ) { + if let None = util::open_link(download_url) { + dialog::error(&format!( + "Failed to open download page. Please visit {}", + download_url + )); + } + } + } + } +} diff --git a/packages/hoppscotch-agent/src-tauri/src/util.rs b/packages/hoppscotch-agent/src-tauri/src/util.rs new file mode 100644 index 0000000000..e383f0b932 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/util.rs @@ -0,0 +1,86 @@ +use std::process::{Command, Stdio}; + +use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, KeyInit}; +use axum::{ + body::Body, + response::{IntoResponse, Response}, +}; +use rand::rngs::OsRng; +use serde::Serialize; + +pub fn open_link(link: &str) -> Option<()> { + let null = Stdio::null(); + + #[cfg(target_os = "windows")] + { + Command::new("rundll32") + .args(["url.dll,FileProtocolHandler", link]) + .stdout(null) + .spawn() + .ok() + .map(|_| ()) + } + + #[cfg(target_os = "macos")] + { + Command::new("open") + .arg(link) + .stdout(null) + .spawn() + .ok() + .map(|_| ()) + } + + #[cfg(target_os = "linux")] + { + Command::new("xdg-open") + .arg(link) + .stdout(null) + .spawn() + .ok() + .map(|_| ()) + } + + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + { + None + } +} + +#[derive(Debug)] +pub struct EncryptedJson { + pub key_b16: String, + pub data: T, +} + +impl IntoResponse for EncryptedJson +where + T: Serialize, +{ + fn into_response(self) -> Response { + let serialized_response = serde_json::to_vec(&self.data) + .expect("Failed serializing response to vec for encryption"); + + let key: [u8; 32] = base16::decode(&self.key_b16).unwrap()[0..32] + .try_into() + .unwrap(); + + let cipher = Aes256Gcm::new(&key.into()); + + let nonce = Aes256Gcm::generate_nonce(&mut OsRng); + + let nonce_b16 = base16::encode_lower(&nonce); + + let encrypted_response = cipher + .encrypt(&nonce, serialized_response.as_slice()) + .expect("Failed encrypting response"); + + let mut response = Response::new(Body::from(encrypted_response)); + let response_headers = response.headers_mut(); + + response_headers.insert("Content-Type", "application/octet-stream".parse().unwrap()); + response_headers.insert("X-Hopp-Nonce", nonce_b16.parse().unwrap()); + + response + } +} diff --git a/packages/hoppscotch-agent/src-tauri/src/webview/error.rs b/packages/hoppscotch-agent/src-tauri/src/webview/error.rs new file mode 100644 index 0000000000..7bfeff5240 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/webview/error.rs @@ -0,0 +1,15 @@ +use std::io; + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum WebViewError { + #[error("Failed to open URL: {0}")] + UrlOpen(#[from] io::Error), + #[error("Failed to download WebView2 installer: {0}")] + Download(String), + #[error("WebView2 installation failed: {0}")] + Installation(String), + #[error("Failed during request: {0}")] + Request(#[from] tauri_plugin_http::reqwest::Error), +} diff --git a/packages/hoppscotch-agent/src-tauri/src/webview/mod.rs b/packages/hoppscotch-agent/src-tauri/src/webview/mod.rs new file mode 100644 index 0000000000..f66279b440 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/webview/mod.rs @@ -0,0 +1,212 @@ +/// The WebView2 Runtime is a critical dependency for Tauri applications on Windows. +/// We need to check for its presence, see [Source: GitHub Issue #59 - Portable windows build](https://github.com/tauri-apps/tauri-action/issues/59#issuecomment-827142638) +/// +/// > "Tauri requires an installer if you define app resources, external binaries or running on environments that do not have Webview2 runtime installed. So I don't think it's a good idea to have a "portable" option since a Tauri binary itself isn't 100% portable." +/// +/// The approach for checking WebView2 installation is based on Microsoft's official documentation, which states: +/// +/// > ###### Detect if a WebView2 Runtime is already installed +/// > +/// > To verify that a WebView2 Runtime is installed, use one of the following approaches: +/// > +/// > * Approach 1: Inspect the `pv (REG_SZ)` regkey for the WebView2 Runtime at both of the following registry locations. The `HKEY_LOCAL_MACHINE` regkey is used for _per-machine_ install. The `HKEY_CURRENT_USER` regkey is used for _per-user_ install. +/// > +/// > For WebView2 applications, at least one of these regkeys must be present and defined with a version greater than 0.0.0.0. If neither regkey exists, or if only one of these regkeys exists but its value is `null`, an empty string, or 0.0.0.0, this means that the WebView2 Runtime isn't installed on the client. Inspect these regkeys to detect whether the WebView2 Runtime is installed, and to get the version of the WebView2 Runtime. Find `pv (REG_SZ)` at the following two locations. +/// > +/// > The two registry locations to inspect on 64-bit Windows: +/// > +/// > ``` +/// > HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5} +/// > +/// > HKEY_CURRENT_USER\Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5} +/// > ``` +/// > +/// > The two registry locations to inspect on 32-bit Windows: +/// > +/// > ``` +/// > HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5} +/// > +/// > HKEY_CURRENT_USER\Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5} +/// > ``` +/// > +/// > * Approach 2: Run [GetAvailableCoreWebView2BrowserVersionString](/microsoft-edge/webview2/reference/win32/webview2-idl#getavailablecorewebview2browserversionstring) and evaluate whether the `versionInfo` is `nullptr`. `nullptr` indicates that the WebView2 Runtime isn't installed. This API returns version information for the WebView2 Runtime or for any installed preview channels of Microsoft Edge (Beta, Dev, or Canary). +/// +/// See: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution?tabs=dotnetcsharp#detect-if-a-webview2-runtime-is-already-installed +/// +/// Our implementation uses Approach 1, checking both the 32-bit (WOW6432Node) and 64-bit registry locations +/// to make sure we have critical dependencis compatibility with different system architectures. +pub mod error; + +use std::{io, ops::Not}; + +use native_dialog::MessageType; + +use crate::{dialog, util}; +use error::WebViewError; + +#[cfg(windows)] +use { + std::io::Cursor, + std::process::Command, + tauri_plugin_http::reqwest, + tempfile::TempDir, + winreg::{ + enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}, + RegKey, + }, +}; + +const TAURI_WEBVIEW_REF: &str = "https://v2.tauri.app/references/webview-versions/"; +const WINDOWS_WEBVIEW_REF: &str = + "https://developer.microsoft.com/microsoft-edge/webview2/#download-section"; + +fn is_available() -> bool { + #[cfg(windows)] + { + const KEY_WOW64: &str = r"SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"; + const KEY: &str = + r"SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"; + + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + + [ + hklm.open_subkey(KEY_WOW64), + hkcu.open_subkey(KEY_WOW64), + hklm.open_subkey(KEY), + hkcu.open_subkey(KEY), + ] + .into_iter() + .any(|result| result.is_ok()) + } + + #[cfg(not(windows))] + { + true + } +} + +fn open_install_website() -> Result<(), WebViewError> { + let url = if cfg!(windows) { + WINDOWS_WEBVIEW_REF + } else { + TAURI_WEBVIEW_REF + }; + + util::open_link(url).map(|_| ()).ok_or_else(|| { + WebViewError::UrlOpen(io::Error::new( + io::ErrorKind::Other, + "Failed to open browser to WebView download section", + )) + }) +} + +#[cfg(windows)] +async fn install() -> Result<(), WebViewError> { + const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"; + const DEFAULT_FILENAME: &str = "MicrosoftEdgeWebview2Setup.exe"; + + let client = reqwest::Client::builder() + .user_agent("Hoppscotch Agent") + .gzip(true) + .build()?; + + let response = client.get(WEBVIEW2_BOOTSTRAPPER_URL).send().await?; + + if !response.status().is_success() { + return Err(WebViewError::Download(format!( + "Failed to download WebView2 bootstrapper. Status: {}", + response.status() + ))); + } + + let filename = + get_filename_from_response(&response).unwrap_or_else(|| DEFAULT_FILENAME.to_owned()); + + let tmp_dir = TempDir::with_prefix("WebView-setup-")?; + let installer_path = tmp_dir.path().join(filename); + + let content = response.bytes().await?; + { + let mut file = std::fs::File::create(&installer_path)?; + io::copy(&mut Cursor::new(content), &mut file)?; + } + + let status = Command::new(&installer_path).args(["/install"]).status()?; + + if !status.success() { + return Err(WebViewError::Installation(format!( + "Installer exited with code `{}`.", + status.code().unwrap_or(-1) + ))); + } + + Ok(()) +} + +#[cfg(windows)] +fn get_filename_from_response(response: &reqwest::Response) -> Option { + response + .headers() + .get("content-disposition") + .and_then(|value| value.to_str().ok()) + .and_then(|value| value.split("filename=").last()) + .map(|name| name.trim().replace('\"', "")) + .or_else(|| { + response + .url() + .path_segments() + .and_then(|segments| segments.last()) + .map(|name| name.to_string()) + }) + .filter(|name| !name.is_empty()) +} + +#[cfg(not(windows))] +async fn install() -> Result<(), WebViewError> { + Err(WebViewError::Installation( + "Unable to auto-install WebView. Please refer to https://v2.tauri.app/references/webview-versions/".to_string(), + )) +} + +pub fn init_webview() { + if is_available() { + return; + } + + if dialog::confirm( + "WebView Error", + "WebView is required for this application to work.\n\n\ + Do you want to install it?", + MessageType::Error, + ) + .not() + { + log::warn!("Declined to setup WebView."); + + std::process::exit(1); + } + + if let Err(e) = tauri::async_runtime::block_on(install()) { + dialog::error(&format!( + "Failed to install WebView: {}\n\n\ + Please install it manually from webpage that should open when you click 'Ok'.\n\n\ + If that doesn't work, please visit Microsoft Edge Webview2 download section.", + e + )); + + if let Err(e) = open_install_website() { + log::warn!("Failed to launch WebView website:\n{}", e); + } + + std::process::exit(1); + } + + if is_available().not() { + dialog::panic( + "Unable to setup WebView:\n\n\ + Please install it manually and relaunch the application.\n\ + https://developer.microsoft.com/microsoft-edge/webview2/#download-section", + ); + } +} diff --git a/packages/hoppscotch-agent/src-tauri/tauri.conf.json b/packages/hoppscotch-agent/src-tauri/tauri.conf.json new file mode 100644 index 0000000000..13dfb3d02d --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/tauri.conf.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://schema.tauri.app/config/2.0.0-rc", + "productName": "Hoppscotch Agent", + "version": "0.1.3", + "identifier": "io.hoppscotch.agent", + "build": { + "beforeDevCommand": "pnpm dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "pnpm build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "title": "Hoppscotch Agent", + "width": 600, + "height": 400, + "center": true, + "resizable": false, + "maximizable": false, + "minimizable": false, + "focus": true, + "alwaysOnTop": true, + "create": false + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "createUpdaterArtifacts": true + }, + "plugins": { + "updater": { + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDRBQzgxQjc3MzJCMjZENEMKUldSTWJiSXlkeHZJU3EvQW1abFVlREVhRDNlM0ZhOVJYaHN4M0FpbXZhcUFzSVdVbG84RWhPa1AK", + "endpoints": [ + "https://releases.hoppscotch.com/hoppscotch-agent.json" + ] + } + } +} diff --git a/packages/hoppscotch-agent/src-tauri/tauri.portable.conf.json b/packages/hoppscotch-agent/src-tauri/tauri.portable.conf.json new file mode 100644 index 0000000000..1797f52deb --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/tauri.portable.conf.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://schema.tauri.app/config/2.0.0-rc", + "productName": "Hoppscotch Agent Portable", + "version": "0.1.3", + "identifier": "io.hoppscotch.agent", + "build": { + "beforeDevCommand": "pnpm dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "pnpm build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "title": "Hoppscotch Agent Portable", + "width": 600, + "height": 400, + "center": true, + "resizable": false, + "maximizable": false, + "minimizable": false, + "focus": true, + "alwaysOnTop": true, + "create": false + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": false, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "createUpdaterArtifacts": false + }, + "plugins": { + "updater": { + "active": false + } + } +} diff --git a/packages/hoppscotch-agent/src/App.vue b/packages/hoppscotch-agent/src/App.vue new file mode 100644 index 0000000000..f58644c25e --- /dev/null +++ b/packages/hoppscotch-agent/src/App.vue @@ -0,0 +1,71 @@ + + + diff --git a/packages/hoppscotch-agent/src/index.css b/packages/hoppscotch-agent/src/index.css new file mode 100644 index 0000000000..b5c61c9567 --- /dev/null +++ b/packages/hoppscotch-agent/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/hoppscotch-agent/src/main.ts b/packages/hoppscotch-agent/src/main.ts new file mode 100644 index 0000000000..9380216e64 --- /dev/null +++ b/packages/hoppscotch-agent/src/main.ts @@ -0,0 +1,13 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' + +import { plugin as HoppUI } from "@hoppscotch/ui" + +import "@hoppscotch/ui/themes.css" + +import "@hoppscotch/ui/style.css" + +createApp(App) + .use(HoppUI) + .mount('#app') diff --git a/packages/hoppscotch-agent/src/vite-env.d.ts b/packages/hoppscotch-agent/src/vite-env.d.ts new file mode 100644 index 0000000000..fc812394b5 --- /dev/null +++ b/packages/hoppscotch-agent/src/vite-env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module "*.vue" { + import type { DefineComponent } from "vue"; + const component: DefineComponent<{}, {}, any>; + export default component; +} diff --git a/packages/hoppscotch-agent/tailwind.config.js b/packages/hoppscotch-agent/tailwind.config.js new file mode 100644 index 0000000000..8d2a79d668 --- /dev/null +++ b/packages/hoppscotch-agent/tailwind.config.js @@ -0,0 +1,6 @@ +import preset from '@hoppscotch/ui/ui-preset' + +export default { + content: ['src/**/*.{vue,html}'], + presets: [preset] +} diff --git a/packages/hoppscotch-agent/tsconfig.json b/packages/hoppscotch-agent/tsconfig.json new file mode 100644 index 0000000000..f82888f3d0 --- /dev/null +++ b/packages/hoppscotch-agent/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/hoppscotch-agent/tsconfig.node.json b/packages/hoppscotch-agent/tsconfig.node.json new file mode 100644 index 0000000000..42872c59f5 --- /dev/null +++ b/packages/hoppscotch-agent/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/hoppscotch-agent/vite.config.ts b/packages/hoppscotch-agent/vite.config.ts new file mode 100644 index 0000000000..9a9a1549b7 --- /dev/null +++ b/packages/hoppscotch-agent/vite.config.ts @@ -0,0 +1,50 @@ +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import tailwindcss from 'tailwindcss'; +import autoprefixer from 'autoprefixer'; +import path from 'path'; +import Icons from "unplugin-icons/vite"; + +const host = process.env.TAURI_DEV_HOST; + +// https://vitejs.dev/config/ +export default defineConfig(async () => ({ + plugins: [ + vue(), + Icons({}) + ], + css: { + postcss: { + plugins: [ + tailwindcss, + autoprefixer, + ], + }, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + }, + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // + // 1. prevent vite from obscuring rust errors + clearScreen: false, + // 2. tauri expects a fixed port, fail if that port is not available + server: { + port: 1420, + strictPort: true, + host: host || false, + hmr: host + ? { + protocol: "ws", + host, + port: 1421, + } + : undefined, + watch: { + // 3. tell vite to ignore watching `src-tauri` + ignored: ["**/src-tauri/**"], + }, + }, +})); diff --git a/packages/hoppscotch-backend/Dockerfile b/packages/hoppscotch-backend/Dockerfile index 34b383d9e6..ccc4e8207a 100644 --- a/packages/hoppscotch-backend/Dockerfile +++ b/packages/hoppscotch-backend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18.8.0 AS builder +FROM node:20.12.2 AS builder WORKDIR /usr/src/app diff --git a/packages/hoppscotch-backend/backend.Caddyfile b/packages/hoppscotch-backend/backend.Caddyfile index d5bd132449..523516b8b5 100644 --- a/packages/hoppscotch-backend/backend.Caddyfile +++ b/packages/hoppscotch-backend/backend.Caddyfile @@ -1,3 +1,8 @@ +{ + admin off + persist_config off +} + :80 :3170 { reverse_proxy localhost:8080 } diff --git a/packages/hoppscotch-backend/cross-env b/packages/hoppscotch-backend/cross-env deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/hoppscotch-backend/eslint b/packages/hoppscotch-backend/eslint deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/hoppscotch-backend/nest-cli.json b/packages/hoppscotch-backend/nest-cli.json index 91bc383046..8faa6fd9e6 100644 --- a/packages/hoppscotch-backend/nest-cli.json +++ b/packages/hoppscotch-backend/nest-cli.json @@ -3,9 +3,7 @@ "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { - "assets": [ - "**/*.hbs" - ], + "assets": [{ "include": "mailer/templates/**/*", "outDir": "dist" }], "watchAssets": true } } diff --git a/packages/hoppscotch-backend/package.json b/packages/hoppscotch-backend/package.json index 8dae80eb2e..71dad0b7d5 100644 --- a/packages/hoppscotch-backend/package.json +++ b/packages/hoppscotch-backend/package.json @@ -1,10 +1,14 @@ { "name": "hoppscotch-backend", - "version": "2024.3.0", + "version": "2024.12.0", "description": "", "author": "", "private": true, "license": "UNLICENSED", + "files": [ + "prisma", + "dist" + ], "scripts": { "prebuild": "rimraf dist", "build": "nest build", @@ -24,85 +28,88 @@ "do-test": "pnpm run test" }, "dependencies": { - "@apollo/server": "4.9.5", - "@nestjs-modules/mailer": "1.9.1", - "@nestjs/apollo": "12.0.9", - "@nestjs/common": "10.2.7", - "@nestjs/config": "3.1.1", - "@nestjs/core": "10.2.7", - "@nestjs/graphql": "12.0.9", - "@nestjs/jwt": "10.1.1", - "@nestjs/passport": "10.0.2", - "@nestjs/platform-express": "10.2.7", - "@nestjs/schedule": "4.0.1", - "@nestjs/throttler": "5.0.1", - "@prisma/client": "5.8.1", - "argon2": "0.30.3", - "bcrypt": "5.1.0", - "cookie": "0.5.0", - "cookie-parser": "1.4.6", - "cron": "3.1.6", - "express": "4.18.2", - "express-session": "1.17.3", - "fp-ts": "2.13.1", - "graphql": "16.8.1", - "graphql-query-complexity": "0.12.0", - "graphql-redis-subscriptions": "2.6.0", + "@apollo/server": "4.11.0", + "@nestjs-modules/mailer": "2.0.2", + "@nestjs/apollo": "12.2.0", + "@nestjs/common": "10.4.4", + "@nestjs/config": "3.2.3", + "@nestjs/core": "10.4.4", + "@nestjs/graphql": "12.2.0", + "@nestjs/jwt": "10.2.0", + "@nestjs/passport": "10.0.3", + "@nestjs/platform-express": "10.4.4", + "@nestjs/schedule": "4.1.1", + "@nestjs/swagger": "7.4.2", + "@nestjs/terminus": "10.2.3", + "@nestjs/throttler": "6.2.1", + "@prisma/client": "5.20.0", + "argon2": "0.41.1", + "bcrypt": "5.1.1", + "class-transformer": "0.5.1", + "class-validator": "0.14.1", + "cookie": "1.0.0", + "cookie-parser": "1.4.7", + "cron": "3.1.7", + "express": "4.21.1", + "express-session": "1.18.1", + "fp-ts": "2.16.9", + "graphql": "16.9.0", + "graphql-query-complexity": "1.0.0", + "graphql-redis-subscriptions": "2.6.1", "graphql-subscriptions": "2.0.0", - "handlebars": "4.7.7", - "io-ts": "2.2.16", - "luxon": "3.2.1", - "nodemailer": "6.9.1", - "passport": "0.6.0", + "handlebars": "4.7.8", + "io-ts": "2.2.21", + "luxon": "3.5.0", + "nodemailer": "6.9.15", + "passport": "0.7.0", "passport-github2": "0.1.12", "passport-google-oauth20": "2.0.0", "passport-jwt": "4.0.1", "passport-local": "1.0.0", - "passport-microsoft": "1.0.0", + "passport-microsoft": "2.1.0", "passport-openidconnect": "^0.1.1", - "posthog-node": "3.6.3", - "prisma": "5.8.1", - "reflect-metadata": "0.1.13", - "rimraf": "3.0.2", - "rxjs": "7.6.0" + "posthog-node": "4.2.0", + "prisma": "5.20.0", + "reflect-metadata": "0.2.2", + "rimraf": "6.0.1", + "rxjs": "7.8.1" }, "devDependencies": { - "@nestjs/cli": "10.2.1", - "@nestjs/schematics": "10.0.3", - "@nestjs/testing": "10.2.7", - "@relmify/jest-fp-ts": "2.0.2", - "@types/argon2": "0.15.0", - "@types/bcrypt": "5.0.0", - "@types/cookie": "0.5.1", - "@types/cookie-parser": "1.4.3", - "@types/express": "4.17.14", - "@types/jest": "29.4.0", - "@types/luxon": "3.2.0", - "@types/node": "18.11.10", - "@types/nodemailer": "6.4.7", - "@types/passport-github2": "1.2.5", - "@types/passport-google-oauth20": "2.0.11", - "@types/passport-jwt": "3.0.8", - "@types/passport-microsoft": "0.0.0", + "@nestjs/cli": "10.4.5", + "@nestjs/schematics": "10.1.4", + "@nestjs/testing": "10.4.4", + "@relmify/jest-fp-ts": "2.1.1", + "@types/bcrypt": "5.0.2", + "@types/cookie": "0.6.0", + "@types/cookie-parser": "1.4.7", + "@types/express": "5.0.0", + "@types/jest": "29.5.13", + "@types/luxon": "3.4.2", + "@types/node": "22.7.5", + "@types/nodemailer": "6.4.16", + "@types/passport-github2": "1.2.9", + "@types/passport-google-oauth20": "2.0.16", + "@types/passport-jwt": "4.0.1", + "@types/passport-microsoft": "1.0.3", "@types/passport-openidconnect": "^0.1.0", - "@types/supertest": "2.0.12", - "@typescript-eslint/eslint-plugin": "5.45.0", - "@typescript-eslint/parser": "5.45.0", + "@types/supertest": "6.0.2", + "@typescript-eslint/eslint-plugin": "8.8.1", + "@typescript-eslint/parser": "8.8.1", "cross-env": "7.0.3", - "eslint": "8.29.0", - "eslint-config-prettier": "8.5.0", - "eslint-plugin-prettier": "4.2.1", - "jest": "29.4.1", - "jest-mock-extended": "3.0.1", + "eslint": "8.57.0", + "eslint-config-prettier": "9.1.0", + "eslint-plugin-prettier": "5.2.1", + "jest": "29.7.0", + "jest-mock-extended": "4.0.0-beta1", "jwt": "link:@types/nestjs/jwt", - "prettier": "2.8.4", + "prettier": "3.3.3", "source-map-support": "0.5.21", - "supertest": "6.3.2", - "ts-jest": "29.0.5", - "ts-loader": "9.4.2", - "ts-node": "10.9.1", - "tsconfig-paths": "4.1.1", - "typescript": "4.9.3" + "supertest": "7.0.0", + "ts-jest": "29.2.5", + "ts-loader": "9.5.1", + "ts-node": "10.9.2", + "tsconfig-paths": "4.2.0", + "typescript": "5.5.4" }, "jest": { "moduleFileExtensions": [ diff --git a/packages/hoppscotch-backend/prisma/migrations/20240519093155_add_last_logged_on_to_user/migration.sql b/packages/hoppscotch-backend/prisma/migrations/20240519093155_add_last_logged_on_to_user/migration.sql new file mode 100644 index 0000000000..5664c4fc0c --- /dev/null +++ b/packages/hoppscotch-backend/prisma/migrations/20240519093155_add_last_logged_on_to_user/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "lastLoggedOn" TIMESTAMP(3); diff --git a/packages/hoppscotch-backend/prisma/migrations/20240520091033_personal_access_tokens/migration.sql b/packages/hoppscotch-backend/prisma/migrations/20240520091033_personal_access_tokens/migration.sql new file mode 100644 index 0000000000..53abda80bd --- /dev/null +++ b/packages/hoppscotch-backend/prisma/migrations/20240520091033_personal_access_tokens/migration.sql @@ -0,0 +1,19 @@ + +-- CreateTable +CREATE TABLE "PersonalAccessToken" ( + "id" TEXT NOT NULL, + "userUid" TEXT NOT NULL, + "label" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expiresOn" TIMESTAMP(3), + "createdOn" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedOn" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "PersonalAccessToken_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "PersonalAccessToken_token_key" ON "PersonalAccessToken"("token"); + +-- AddForeignKey +ALTER TABLE "PersonalAccessToken" ADD CONSTRAINT "PersonalAccessToken_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "User"("uid") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/hoppscotch-backend/prisma/migrations/20240621062554_user_last_active_on/migration.sql b/packages/hoppscotch-backend/prisma/migrations/20240621062554_user_last_active_on/migration.sql new file mode 100644 index 0000000000..9e0ff1c934 --- /dev/null +++ b/packages/hoppscotch-backend/prisma/migrations/20240621062554_user_last_active_on/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "lastActiveOn" TIMESTAMP(3); diff --git a/packages/hoppscotch-backend/prisma/migrations/20240725043411_infra_config_encryption/migration.sql b/packages/hoppscotch-backend/prisma/migrations/20240725043411_infra_config_encryption/migration.sql new file mode 100644 index 0000000000..71cf28a016 --- /dev/null +++ b/packages/hoppscotch-backend/prisma/migrations/20240725043411_infra_config_encryption/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "InfraConfig" ADD COLUMN "isEncrypted" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/hoppscotch-backend/prisma/migrations/20240726121956_infra_token/migration.sql b/packages/hoppscotch-backend/prisma/migrations/20240726121956_infra_token/migration.sql new file mode 100644 index 0000000000..10971ba62b --- /dev/null +++ b/packages/hoppscotch-backend/prisma/migrations/20240726121956_infra_token/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "InfraToken" ( + "id" TEXT NOT NULL, + "creatorUid" TEXT NOT NULL, + "label" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expiresOn" TIMESTAMP(3), + "createdOn" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedOn" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "InfraToken_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "InfraToken_token_key" ON "InfraToken"("token"); diff --git a/packages/hoppscotch-backend/prisma/migrations/20241118054346_infra_config_sync_with_env_file/migration.sql b/packages/hoppscotch-backend/prisma/migrations/20241118054346_infra_config_sync_with_env_file/migration.sql new file mode 100644 index 0000000000..23ccdb576e --- /dev/null +++ b/packages/hoppscotch-backend/prisma/migrations/20241118054346_infra_config_sync_with_env_file/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "InfraConfig" DROP COLUMN "active", +ADD COLUMN "lastSyncedEnvFileValue" TEXT; diff --git a/packages/hoppscotch-backend/prisma/schema.prisma b/packages/hoppscotch-backend/prisma/schema.prisma index 3225d9ccc0..45d1773721 100644 --- a/packages/hoppscotch-backend/prisma/schema.prisma +++ b/packages/hoppscotch-backend/prisma/schema.prisma @@ -41,31 +41,31 @@ model TeamInvitation { } model TeamCollection { - id String @id @default(cuid()) + id String @id @default(cuid()) parentID String? data Json? - parent TeamCollection? @relation("TeamCollectionChildParent", fields: [parentID], references: [id]) - children TeamCollection[] @relation("TeamCollectionChildParent") + parent TeamCollection? @relation("TeamCollectionChildParent", fields: [parentID], references: [id]) + children TeamCollection[] @relation("TeamCollectionChildParent") requests TeamRequest[] teamID String - team Team @relation(fields: [teamID], references: [id], onDelete: Cascade) + team Team @relation(fields: [teamID], references: [id], onDelete: Cascade) title String orderIndex Int - createdOn DateTime @default(now()) @db.Timestamp(3) - updatedOn DateTime @updatedAt @db.Timestamp(3) + createdOn DateTime @default(now()) @db.Timestamp(3) + updatedOn DateTime @updatedAt @db.Timestamp(3) } model TeamRequest { - id String @id @default(cuid()) + id String @id @default(cuid()) collectionID String - collection TeamCollection @relation(fields: [collectionID], references: [id], onDelete: Cascade) + collection TeamCollection @relation(fields: [collectionID], references: [id], onDelete: Cascade) teamID String - team Team @relation(fields: [teamID], references: [id], onDelete: Cascade) + team Team @relation(fields: [teamID], references: [id], onDelete: Cascade) title String request Json orderIndex Int - createdOn DateTime @default(now()) @db.Timestamp(3) - updatedOn DateTime @updatedAt @db.Timestamp(3) + createdOn DateTime @default(now()) @db.Timestamp(3) + updatedOn DateTime @updatedAt @db.Timestamp(3) } model Shortcode { @@ -89,24 +89,27 @@ model TeamEnvironment { } model User { - uid String @id @default(cuid()) - displayName String? - email String? @unique - photoURL String? - isAdmin Boolean @default(false) - refreshToken String? - providerAccounts Account[] - VerificationToken VerificationToken[] - settings UserSettings? - UserHistory UserHistory[] - UserEnvironments UserEnvironment[] - userCollections UserCollection[] - userRequests UserRequest[] - currentRESTSession Json? - currentGQLSession Json? - createdOn DateTime @default(now()) @db.Timestamp(3) - invitedUsers InvitedUsers[] - shortcodes Shortcode[] + uid String @id @default(cuid()) + displayName String? + email String? @unique + photoURL String? + isAdmin Boolean @default(false) + refreshToken String? + providerAccounts Account[] + VerificationToken VerificationToken[] + settings UserSettings? + UserHistory UserHistory[] + UserEnvironments UserEnvironment[] + userCollections UserCollection[] + userRequests UserRequest[] + currentRESTSession Json? + currentGQLSession Json? + lastLoggedOn DateTime? @db.Timestamp(3) + lastActiveOn DateTime? @db.Timestamp(3) + createdOn DateTime @default(now()) @db.Timestamp(3) + invitedUsers InvitedUsers[] + shortcodes Shortcode[] + personalAccessTokens PersonalAccessToken[] } model Account { @@ -211,10 +214,32 @@ enum TeamMemberRole { } model InfraConfig { - id String @id @default(cuid()) - name String @unique - value String? - active Boolean @default(true) // Use case: Let's say, Admin wants to disable Google SSO, but doesn't want to delete the config - createdOn DateTime @default(now()) @db.Timestamp(3) - updatedOn DateTime @updatedAt @db.Timestamp(3) + id String @id @default(cuid()) + name String @unique + value String? + lastSyncedEnvFileValue String? + isEncrypted Boolean @default(false) // Use case: Let's say, Admin wants to store a Secret Key, but doesn't want to store it in plain text in `value` column + createdOn DateTime @default(now()) @db.Timestamp(3) + updatedOn DateTime @updatedAt @db.Timestamp(3) +} + +model PersonalAccessToken { + id String @id @default(cuid()) + userUid String + user User @relation(fields: [userUid], references: [uid], onDelete: Cascade) + label String + token String @unique @default(uuid()) + expiresOn DateTime? @db.Timestamp(3) + createdOn DateTime @default(now()) @db.Timestamp(3) + updatedOn DateTime @updatedAt @db.Timestamp(3) +} + +model InfraToken { + id String @id @default(cuid()) + creatorUid String + label String + token String @unique @default(uuid()) + expiresOn DateTime? @db.Timestamp(3) + createdOn DateTime @default(now()) @db.Timestamp(3) + updatedOn DateTime @default(now()) @db.Timestamp(3) } diff --git a/packages/hoppscotch-backend/prod_run.mjs b/packages/hoppscotch-backend/prod_run.mjs index 2d4a8c1d97..6b375991b8 100644 --- a/packages/hoppscotch-backend/prod_run.mjs +++ b/packages/hoppscotch-backend/prod_run.mjs @@ -39,8 +39,8 @@ const caddyProcess = runChildProcessWithPrefix( 'App/Admin Dashboard Caddy', ); const backendProcess = runChildProcessWithPrefix( - 'pnpm', - ['run', 'start:prod'], + 'node', + ['/dist/backend/dist/main.js'], 'Backend Server', ); diff --git a/packages/hoppscotch-backend/src/access-token/access-token.controller.ts b/packages/hoppscotch-backend/src/access-token/access-token.controller.ts new file mode 100644 index 0000000000..4b1bd84600 --- /dev/null +++ b/packages/hoppscotch-backend/src/access-token/access-token.controller.ts @@ -0,0 +1,107 @@ +import { + BadRequestException, + Body, + Controller, + Delete, + Get, + HttpStatus, + Param, + ParseIntPipe, + Post, + Query, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { AccessTokenService } from './access-token.service'; +import { CreateAccessTokenDto } from './dto/create-access-token.dto'; +import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; +import * as E from 'fp-ts/Either'; +import { throwHTTPErr } from 'src/utils'; +import { GqlUser } from 'src/decorators/gql-user.decorator'; +import { AuthUser } from 'src/types/AuthUser'; +import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard'; +import { PATAuthGuard } from 'src/guards/rest-pat-auth.guard'; +import { AccessTokenInterceptor } from 'src/interceptors/access-token.interceptor'; +import { TeamEnvironmentsService } from 'src/team-environments/team-environments.service'; +import { TeamCollectionService } from 'src/team-collection/team-collection.service'; +import { ACCESS_TOKENS_INVALID_DATA_ID } from 'src/errors'; +import { createCLIErrorResponse } from './helper'; + +@UseGuards(ThrottlerBehindProxyGuard) +@Controller({ path: 'access-tokens', version: '1' }) +export class AccessTokenController { + constructor( + private readonly accessTokenService: AccessTokenService, + private readonly teamCollectionService: TeamCollectionService, + private readonly teamEnvironmentsService: TeamEnvironmentsService, + ) {} + + @Post('create') + @UseGuards(JwtAuthGuard) + async createPAT( + @GqlUser() user: AuthUser, + @Body() createAccessTokenDto: CreateAccessTokenDto, + ) { + const result = await this.accessTokenService.createPAT( + createAccessTokenDto, + user, + ); + if (E.isLeft(result)) throwHTTPErr(result.left); + return result.right; + } + + @Delete('revoke') + @UseGuards(JwtAuthGuard) + async deletePAT(@Query('id') id: string) { + const result = await this.accessTokenService.deletePAT(id); + + if (E.isLeft(result)) throwHTTPErr(result.left); + return result.right; + } + + @Get('list') + @UseGuards(JwtAuthGuard) + async listAllUserPAT( + @GqlUser() user: AuthUser, + @Query('offset', ParseIntPipe) offset: number, + @Query('limit', ParseIntPipe) limit: number, + ) { + return await this.accessTokenService.listAllUserPAT( + user.uid, + offset, + limit, + ); + } + + @Get('collection/:id') + @UseGuards(PATAuthGuard) + @UseInterceptors(AccessTokenInterceptor) + async fetchCollection(@GqlUser() user: AuthUser, @Param('id') id: string) { + const res = await this.teamCollectionService.getCollectionForCLI( + id, + user.uid, + ); + + if (E.isLeft(res)) + throw new BadRequestException( + createCLIErrorResponse(ACCESS_TOKENS_INVALID_DATA_ID), + ); + return res.right; + } + + @Get('environment/:id') + @UseGuards(PATAuthGuard) + @UseInterceptors(AccessTokenInterceptor) + async fetchEnvironment(@GqlUser() user: AuthUser, @Param('id') id: string) { + const res = await this.teamEnvironmentsService.getTeamEnvironmentForCLI( + id, + user.uid, + ); + + if (E.isLeft(res)) + throw new BadRequestException( + createCLIErrorResponse(ACCESS_TOKENS_INVALID_DATA_ID), + ); + return res.right; + } +} diff --git a/packages/hoppscotch-backend/src/access-token/access-token.module.ts b/packages/hoppscotch-backend/src/access-token/access-token.module.ts new file mode 100644 index 0000000000..344f6f07f5 --- /dev/null +++ b/packages/hoppscotch-backend/src/access-token/access-token.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { AccessTokenController } from './access-token.controller'; +import { PrismaModule } from 'src/prisma/prisma.module'; +import { AccessTokenService } from './access-token.service'; +import { TeamCollectionModule } from 'src/team-collection/team-collection.module'; +import { TeamEnvironmentsModule } from 'src/team-environments/team-environments.module'; +import { TeamModule } from 'src/team/team.module'; + +@Module({ + imports: [ + PrismaModule, + TeamCollectionModule, + TeamEnvironmentsModule, + TeamModule, + ], + controllers: [AccessTokenController], + providers: [AccessTokenService], + exports: [AccessTokenService], +}) +export class AccessTokenModule {} diff --git a/packages/hoppscotch-backend/src/access-token/access-token.service.spec.ts b/packages/hoppscotch-backend/src/access-token/access-token.service.spec.ts new file mode 100644 index 0000000000..0c319c839a --- /dev/null +++ b/packages/hoppscotch-backend/src/access-token/access-token.service.spec.ts @@ -0,0 +1,196 @@ +import { AccessTokenService } from './access-token.service'; +import { mockDeep, mockReset } from 'jest-mock-extended'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { + ACCESS_TOKEN_EXPIRY_INVALID, + ACCESS_TOKEN_LABEL_SHORT, + ACCESS_TOKEN_NOT_FOUND, +} from 'src/errors'; +import { AuthUser } from 'src/types/AuthUser'; +import { PersonalAccessToken } from '@prisma/client'; +import { AccessToken } from 'src/types/AccessToken'; +import { HttpStatus } from '@nestjs/common'; + +const mockPrisma = mockDeep(); + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +const accessTokenService = new AccessTokenService(mockPrisma); + +const currentTime = new Date(); + +const user: AuthUser = { + uid: '123344', + email: 'dwight@dundermifflin.com', + displayName: 'Dwight Schrute', + photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute', + isAdmin: false, + refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + createdOn: currentTime, + currentGQLSession: {}, + currentRESTSession: {}, + lastLoggedOn: currentTime, + lastActiveOn: currentTime, +}; + +const PATCreatedOn = new Date(); +const expiryInDays = 7; +const PATExpiresOn = new Date( + PATCreatedOn.getTime() + expiryInDays * 24 * 60 * 60 * 1000, +); + +const userAccessToken: PersonalAccessToken = { + id: 'skfvhj8uvdfivb', + userUid: user.uid, + label: 'test', + token: '0140e328-b187-4823-ae4b-ed4bec832ac2', + expiresOn: PATExpiresOn, + createdOn: PATCreatedOn, + updatedOn: new Date(), +}; + +const userAccessTokenCasted: AccessToken = { + id: userAccessToken.id, + label: userAccessToken.label, + createdOn: userAccessToken.createdOn, + lastUsedOn: userAccessToken.updatedOn, + expiresOn: userAccessToken.expiresOn, +}; + +beforeEach(() => { + mockReset(mockPrisma); +}); + +describe('AccessTokenService', () => { + describe('createPAT', () => { + test('should throw ACCESS_TOKEN_LABEL_SHORT if label is too short', async () => { + const result = await accessTokenService.createPAT( + { + label: 'a', + expiryInDays: 7, + }, + user, + ); + expect(result).toEqualLeft({ + message: ACCESS_TOKEN_LABEL_SHORT, + statusCode: HttpStatus.BAD_REQUEST, + }); + }); + + test('should throw ACCESS_TOKEN_EXPIRY_INVALID if expiry date is invalid', async () => { + const result = await accessTokenService.createPAT( + { + label: 'test', + expiryInDays: 9, + }, + user, + ); + expect(result).toEqualLeft({ + message: ACCESS_TOKEN_EXPIRY_INVALID, + statusCode: HttpStatus.BAD_REQUEST, + }); + }); + + test('should successfully create a new Access Token', async () => { + mockPrisma.personalAccessToken.create.mockResolvedValueOnce( + userAccessToken, + ); + + const result = await accessTokenService.createPAT( + { + label: userAccessToken.label, + expiryInDays, + }, + user, + ); + expect(result).toEqualRight({ + token: `pat-${userAccessToken.token}`, + info: userAccessTokenCasted, + }); + }); + }); + + describe('deletePAT', () => { + test('should throw ACCESS_TOKEN_NOT_FOUND if Access Token is not found', async () => { + mockPrisma.personalAccessToken.delete.mockRejectedValueOnce( + 'RecordNotFound', + ); + + const result = await accessTokenService.deletePAT(userAccessToken.id); + expect(result).toEqualLeft({ + message: ACCESS_TOKEN_NOT_FOUND, + statusCode: HttpStatus.NOT_FOUND, + }); + }); + + test('should successfully delete a new Access Token', async () => { + mockPrisma.personalAccessToken.delete.mockResolvedValueOnce( + userAccessToken, + ); + + const result = await accessTokenService.deletePAT(userAccessToken.id); + expect(result).toEqualRight(true); + }); + }); + + describe('listAllUserPAT', () => { + test('should successfully return a list of user Access Tokens', async () => { + mockPrisma.personalAccessToken.findMany.mockResolvedValueOnce([ + userAccessToken, + ]); + + const result = await accessTokenService.listAllUserPAT(user.uid, 0, 10); + expect(result).toEqual([userAccessTokenCasted]); + }); + }); + + describe('getUserPAT', () => { + test('should throw ACCESS_TOKEN_NOT_FOUND if Access Token is not found', async () => { + mockPrisma.personalAccessToken.findUniqueOrThrow.mockRejectedValueOnce( + 'NotFoundError', + ); + + const result = await accessTokenService.getUserPAT(userAccessToken.token); + expect(result).toEqualLeft(ACCESS_TOKEN_NOT_FOUND); + }); + + test('should successfully return a user Access Tokens', async () => { + mockPrisma.personalAccessToken.findUniqueOrThrow.mockResolvedValueOnce({ + ...userAccessToken, + user, + } as any); + + const result = await accessTokenService.getUserPAT( + `pat-${userAccessToken.token}`, + ); + expect(result).toEqualRight({ + user, + ...userAccessToken, + } as any); + }); + }); + + describe('updateLastUsedforPAT', () => { + test('should throw ACCESS_TOKEN_NOT_FOUND if Access Token is not found', async () => { + mockPrisma.personalAccessToken.update.mockRejectedValueOnce( + 'RecordNotFound', + ); + + const result = await accessTokenService.updateLastUsedForPAT( + userAccessToken.token, + ); + expect(result).toEqualLeft(ACCESS_TOKEN_NOT_FOUND); + }); + + test('should successfully update lastUsedOn for a user Access Tokens', async () => { + mockPrisma.personalAccessToken.update.mockResolvedValueOnce( + userAccessToken, + ); + + const result = await accessTokenService.updateLastUsedForPAT( + `pat-${userAccessToken.token}`, + ); + expect(result).toEqualRight(userAccessTokenCasted); + }); + }); +}); diff --git a/packages/hoppscotch-backend/src/access-token/access-token.service.ts b/packages/hoppscotch-backend/src/access-token/access-token.service.ts new file mode 100644 index 0000000000..e6a9977464 --- /dev/null +++ b/packages/hoppscotch-backend/src/access-token/access-token.service.ts @@ -0,0 +1,190 @@ +import { HttpStatus, Injectable } from '@nestjs/common'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { CreateAccessTokenDto } from './dto/create-access-token.dto'; +import { AuthUser } from 'src/types/AuthUser'; +import { calculateExpirationDate, isValidLength } from 'src/utils'; +import * as E from 'fp-ts/Either'; +import { + ACCESS_TOKEN_EXPIRY_INVALID, + ACCESS_TOKEN_LABEL_SHORT, + ACCESS_TOKEN_NOT_FOUND, +} from 'src/errors'; +import { CreateAccessTokenResponse } from './helper'; +import { PersonalAccessToken } from '@prisma/client'; +import { AccessToken } from 'src/types/AccessToken'; +@Injectable() +export class AccessTokenService { + constructor(private readonly prisma: PrismaService) {} + + TITLE_LENGTH = 3; + VALID_TOKEN_DURATIONS = [7, 30, 60, 90]; + TOKEN_PREFIX = 'pat-'; + + /** + * Validate the expiration date of the token + * + * @param expiresOn Number of days the token is valid for + * @returns Boolean indicating if the expiration date is valid + */ + private validateExpirationDate(expiresOn: null | number) { + if (expiresOn === null || this.VALID_TOKEN_DURATIONS.includes(expiresOn)) + return true; + return false; + } + + /** + * Typecast a database PersonalAccessToken to a AccessToken model + * @param token database PersonalAccessToken + * @returns AccessToken model + */ + private cast(token: PersonalAccessToken): AccessToken { + return { + id: token.id, + label: token.label, + createdOn: token.createdOn, + expiresOn: token.expiresOn, + lastUsedOn: token.updatedOn, + }; + } + + /** + * Extract UUID from the token + * + * @param token Personal Access Token + * @returns UUID of the token + */ + private extractUUID(token): string | null { + if (!token.startsWith(this.TOKEN_PREFIX)) return null; + return token.slice(this.TOKEN_PREFIX.length); + } + + /** + * Create a Personal Access Token + * + * @param createAccessTokenDto DTO for creating a Personal Access Token + * @param user AuthUser object + * @returns Either of the created token or error message + */ + async createPAT(createAccessTokenDto: CreateAccessTokenDto, user: AuthUser) { + const isTitleValid = isValidLength( + createAccessTokenDto.label, + this.TITLE_LENGTH, + ); + if (!isTitleValid) + return E.left({ + message: ACCESS_TOKEN_LABEL_SHORT, + statusCode: HttpStatus.BAD_REQUEST, + }); + + if (!this.validateExpirationDate(createAccessTokenDto.expiryInDays)) + return E.left({ + message: ACCESS_TOKEN_EXPIRY_INVALID, + statusCode: HttpStatus.BAD_REQUEST, + }); + + const createdPAT = await this.prisma.personalAccessToken.create({ + data: { + userUid: user.uid, + label: createAccessTokenDto.label, + expiresOn: calculateExpirationDate(createAccessTokenDto.expiryInDays), + }, + }); + + const res: CreateAccessTokenResponse = { + token: `${this.TOKEN_PREFIX}${createdPAT.token}`, + info: this.cast(createdPAT), + }; + + return E.right(res); + } + + /** + * Delete a Personal Access Token + * + * @param accessTokenID ID of the Personal Access Token + * @returns Either of true or error message + */ + async deletePAT(accessTokenID: string) { + try { + await this.prisma.personalAccessToken.delete({ + where: { id: accessTokenID }, + }); + return E.right(true); + } catch { + return E.left({ + message: ACCESS_TOKEN_NOT_FOUND, + statusCode: HttpStatus.NOT_FOUND, + }); + } + } + + /** + * List all Personal Access Tokens of a user + * + * @param userUid UID of the user + * @param offset Offset for pagination + * @param limit Limit for pagination + * @returns Either of the list of Personal Access Tokens or error message + */ + async listAllUserPAT(userUid: string, offset: number, limit: number) { + const userPATs = await this.prisma.personalAccessToken.findMany({ + where: { + userUid: userUid, + }, + skip: offset, + take: limit, + orderBy: { + createdOn: 'desc', + }, + }); + + const userAccessTokenList = userPATs.map((pat) => this.cast(pat)); + + return userAccessTokenList; + } + + /** + * Get a Personal Access Token + * + * @param accessToken Personal Access Token + * @returns Either of the Personal Access Token or error message + */ + async getUserPAT(accessToken: string) { + const extractedToken = this.extractUUID(accessToken); + if (!extractedToken) return E.left(ACCESS_TOKEN_NOT_FOUND); + + try { + const userPAT = await this.prisma.personalAccessToken.findUniqueOrThrow({ + where: { token: extractedToken }, + include: { user: true }, + }); + return E.right(userPAT); + } catch { + return E.left(ACCESS_TOKEN_NOT_FOUND); + } + } + + /** + * Update the last used date of a Personal Access Token + * + * @param token Personal Access Token + * @returns Either of the updated Personal Access Token or error message + */ + async updateLastUsedForPAT(token: string) { + const extractedToken = this.extractUUID(token); + if (!extractedToken) return E.left(ACCESS_TOKEN_NOT_FOUND); + + try { + const updatedAccessToken = await this.prisma.personalAccessToken.update({ + where: { token: extractedToken }, + data: { + updatedOn: new Date(), + }, + }); + + return E.right(this.cast(updatedAccessToken)); + } catch { + return E.left(ACCESS_TOKEN_NOT_FOUND); + } + } +} diff --git a/packages/hoppscotch-backend/src/access-token/dto/create-access-token.dto.ts b/packages/hoppscotch-backend/src/access-token/dto/create-access-token.dto.ts new file mode 100644 index 0000000000..d837a16a4d --- /dev/null +++ b/packages/hoppscotch-backend/src/access-token/dto/create-access-token.dto.ts @@ -0,0 +1,5 @@ +// Inputs to create a new PAT +export class CreateAccessTokenDto { + label: string; + expiryInDays: number | null; +} diff --git a/packages/hoppscotch-backend/src/access-token/helper.ts b/packages/hoppscotch-backend/src/access-token/helper.ts new file mode 100644 index 0000000000..dc52ed5176 --- /dev/null +++ b/packages/hoppscotch-backend/src/access-token/helper.ts @@ -0,0 +1,17 @@ +import { AccessToken } from 'src/types/AccessToken'; + +// Response type of PAT creation method +export type CreateAccessTokenResponse = { + token: string; + info: AccessToken; +}; + +// Response type of any error in PAT module +export type CLIErrorResponse = { + reason: string; +}; + +// Return a CLIErrorResponse object +export function createCLIErrorResponse(reason: string): CLIErrorResponse { + return { reason }; +} diff --git a/packages/hoppscotch-backend/src/admin/admin.module.ts b/packages/hoppscotch-backend/src/admin/admin.module.ts index 9b4afd5321..aa5c0d1540 100644 --- a/packages/hoppscotch-backend/src/admin/admin.module.ts +++ b/packages/hoppscotch-backend/src/admin/admin.module.ts @@ -12,6 +12,7 @@ import { TeamRequestModule } from '../team-request/team-request.module'; import { InfraResolver } from './infra.resolver'; import { ShortcodeModule } from 'src/shortcode/shortcode.module'; import { InfraConfigModule } from 'src/infra-config/infra-config.module'; +import { UserHistoryModule } from 'src/user-history/user-history.module'; @Module({ imports: [ @@ -25,6 +26,7 @@ import { InfraConfigModule } from 'src/infra-config/infra-config.module'; TeamRequestModule, ShortcodeModule, InfraConfigModule, + UserHistoryModule, ], providers: [InfraResolver, AdminResolver, AdminService], exports: [AdminService], diff --git a/packages/hoppscotch-backend/src/admin/admin.resolver.ts b/packages/hoppscotch-backend/src/admin/admin.resolver.ts index bfc7af888f..35e0271860 100644 --- a/packages/hoppscotch-backend/src/admin/admin.resolver.ts +++ b/packages/hoppscotch-backend/src/admin/admin.resolver.ts @@ -117,9 +117,8 @@ export class AdminResolver { }) userUIDs: string[], ): Promise { - const deletionResults = await this.adminService.removeUserAccounts( - userUIDs, - ); + const deletionResults = + await this.adminService.removeUserAccounts(userUIDs); if (E.isLeft(deletionResults)) throwErr(deletionResults.left); return deletionResults.right; } @@ -360,6 +359,16 @@ export class AdminResolver { return true; } + @Mutation(() => Boolean, { + description: 'Revoke all User History', + }) + @UseGuards(GqlAuthGuard, GqlAdminGuard) + async revokeAllUserHistoryByAdmin(): Promise { + const isDeleted = await this.adminService.deleteAllUserHistory(); + if (E.isLeft(isDeleted)) throwErr(isDeleted.left); + return true; + } + /* Subscriptions */ @Subscription(() => InvitedUser, { diff --git a/packages/hoppscotch-backend/src/admin/admin.service.spec.ts b/packages/hoppscotch-backend/src/admin/admin.service.spec.ts index a7ebc06825..a4370d44b7 100644 --- a/packages/hoppscotch-backend/src/admin/admin.service.spec.ts +++ b/packages/hoppscotch-backend/src/admin/admin.service.spec.ts @@ -22,6 +22,7 @@ import { ShortcodeService } from 'src/shortcode/shortcode.service'; import { ConfigService } from '@nestjs/config'; import { OffsetPaginationArgs } from 'src/types/input-types.args'; import * as E from 'fp-ts/Either'; +import { UserHistoryService } from 'src/user-history/user-history.service'; const mockPrisma = mockDeep(); const mockPubSub = mockDeep(); @@ -34,6 +35,7 @@ const mockTeamCollectionService = mockDeep(); const mockMailerService = mockDeep(); const mockShortcodeService = mockDeep(); const mockConfigService = mockDeep(); +const mockUserHistoryService = mockDeep(); const adminService = new AdminService( mockUserService, @@ -47,6 +49,7 @@ const adminService = new AdminService( mockMailerService, mockShortcodeService, mockConfigService, + mockUserHistoryService, ); const invitedUsers: InvitedUsers[] = [ @@ -74,6 +77,8 @@ const dbAdminUsers: DbUser[] = [ refreshToken: 'refreshToken', currentRESTSession: '', currentGQLSession: '', + lastLoggedOn: new Date(), + lastActiveOn: new Date(), createdOn: new Date(), }, { @@ -85,20 +90,11 @@ const dbAdminUsers: DbUser[] = [ refreshToken: 'refreshToken', currentRESTSession: '', currentGQLSession: '', + lastLoggedOn: new Date(), + lastActiveOn: new Date(), createdOn: new Date(), }, ]; -const dbNonAminUser: DbUser = { - uid: 'uid 3', - displayName: 'displayName', - email: 'email@email.com', - photoURL: 'photoURL', - isAdmin: false, - refreshToken: 'refreshToken', - currentRESTSession: '', - currentGQLSession: '', - createdOn: new Date(), -}; describe('AdminService', () => { describe('fetchInvitedUsers', () => { @@ -121,6 +117,7 @@ describe('AdminService', () => { NOT: { inviteeEmail: { in: [dbAdminUsers[0].email], + mode: 'insensitive', }, }, }, @@ -229,7 +226,10 @@ describe('AdminService', () => { expect(mockPrisma.invitedUsers.deleteMany).toHaveBeenCalledWith({ where: { - inviteeEmail: { in: [invitedUsers[0].inviteeEmail] }, + inviteeEmail: { + in: [invitedUsers[0].inviteeEmail], + mode: 'insensitive', + }, }, }); expect(result).toEqualRight(true); @@ -295,4 +295,15 @@ describe('AdminService', () => { expect(result).toEqual(10); }); }); + + describe('deleteAllUserHistory', () => { + test('should resolve right and delete all user history', async () => { + mockUserHistoryService.deleteAllHistories.mockResolvedValueOnce( + E.right(true), + ); + + const result = await adminService.deleteAllUserHistory(); + expect(result).toEqualRight(true); + }); + }); }); diff --git a/packages/hoppscotch-backend/src/admin/admin.service.ts b/packages/hoppscotch-backend/src/admin/admin.service.ts index 0bed661328..8bd257d802 100644 --- a/packages/hoppscotch-backend/src/admin/admin.service.ts +++ b/packages/hoppscotch-backend/src/admin/admin.service.ts @@ -31,6 +31,7 @@ import { ShortcodeService } from 'src/shortcode/shortcode.service'; import { ConfigService } from '@nestjs/config'; import { OffsetPaginationArgs } from 'src/types/input-types.args'; import { UserDeletionResult } from 'src/user/user.model'; +import { UserHistoryService } from 'src/user-history/user-history.service'; @Injectable() export class AdminService { @@ -46,6 +47,7 @@ export class AdminService { private readonly mailerService: MailerService, private readonly shortcodeService: ShortcodeService, private readonly configService: ConfigService, + private readonly userHistoryService: UserHistoryService, ) {} /** @@ -89,12 +91,17 @@ export class AdminService { adminEmail: string, inviteeEmail: string, ) { - if (inviteeEmail == adminEmail) return E.left(DUPLICATE_EMAIL); + if (inviteeEmail.toLowerCase() == adminEmail.toLowerCase()) { + return E.left(DUPLICATE_EMAIL); + } if (!validateEmail(inviteeEmail)) return E.left(INVALID_EMAIL); const alreadyInvitedUser = await this.prisma.invitedUsers.findFirst({ where: { - inviteeEmail: inviteeEmail, + inviteeEmail: { + equals: inviteeEmail, + mode: 'insensitive', + }, }, }); if (alreadyInvitedUser != null) return E.left(USER_ALREADY_INVITED); @@ -156,10 +163,17 @@ export class AdminService { * @returns an Either of boolean or error string */ async revokeUserInvitations(inviteeEmails: string[]) { + const areAllEmailsValid = inviteeEmails.every((email) => + validateEmail(email), + ); + if (!areAllEmailsValid) { + return E.left(INVALID_EMAIL); + } + try { await this.prisma.invitedUsers.deleteMany({ where: { - inviteeEmail: { in: inviteeEmails }, + inviteeEmail: { in: inviteeEmails, mode: 'insensitive' }, }, }); return E.right(true); @@ -189,6 +203,7 @@ export class AdminService { NOT: { inviteeEmail: { in: userEmailObjs.map((user) => user.email), + mode: 'insensitive', }, }, }, @@ -218,9 +233,8 @@ export class AdminService { * @returns a count of team members */ async membersCountInTeam(teamID: string) { - const teamMembersCount = await this.teamService.getCountOfMembersInTeam( - teamID, - ); + const teamMembersCount = + await this.teamService.getCountOfMembersInTeam(teamID); return teamMembersCount; } @@ -263,9 +277,8 @@ export class AdminService { * @returns an array team invitations */ async pendingInvitationCountInTeam(teamID: string) { - const invitations = await this.teamInvitationService.getTeamInvitations( - teamID, - ); + const invitations = + await this.teamInvitationService.getTeamInvitations(teamID); return invitations; } @@ -414,7 +427,7 @@ export class AdminService { * Remove a user account by UID * @param userUid User UID * @returns an Either of boolean or error - * @deprecated use removeUserAccounts instead + * */ async removeUserAccount(userUid: string) { const user = await this.userService.findUserById(userUid); @@ -601,9 +614,8 @@ export class AdminService { * @returns an Either of boolean or error */ async revokeTeamInviteByID(inviteID: string) { - const teamInvite = await this.teamInvitationService.revokeInvitation( - inviteID, - ); + const teamInvite = + await this.teamInvitationService.revokeInvitation(inviteID); if (E.isLeft(teamInvite)) return E.left(teamInvite.left); @@ -640,4 +652,15 @@ export class AdminService { if (E.isLeft(result)) return E.left(result.left); return E.right(result.right); } + + /** + * Delete all user history + * @returns Boolean on successful deletion + */ + async deleteAllUserHistory() { + const result = await this.userHistoryService.deleteAllHistories(); + + if (E.isLeft(result)) return E.left(result.left); + return E.right(result.right); + } } diff --git a/packages/hoppscotch-backend/src/admin/infra.resolver.ts b/packages/hoppscotch-backend/src/admin/infra.resolver.ts index 1c08e9a2fb..045c1a3f01 100644 --- a/packages/hoppscotch-backend/src/admin/infra.resolver.ts +++ b/packages/hoppscotch-backend/src/admin/infra.resolver.ts @@ -214,9 +214,8 @@ export class InfraResolver { }) teamID: string, ) { - const invitations = await this.adminService.pendingInvitationCountInTeam( - teamID, - ); + const invitations = + await this.adminService.pendingInvitationCountInTeam(teamID); return invitations; } @@ -352,11 +351,48 @@ export class InfraResolver { }) providerInfo: EnableAndDisableSSOArgs[], ) { - const isUpdated = await this.infraConfigService.enableAndDisableSSO( - providerInfo, - ); + const isUpdated = + await this.infraConfigService.enableAndDisableSSO(providerInfo); if (E.isLeft(isUpdated)) throwErr(isUpdated.left); return true; } + + @Mutation(() => Boolean, { + description: 'Enable or Disable SMTP for sending emails', + }) + @UseGuards(GqlAuthGuard, GqlAdminGuard) + async toggleSMTP( + @Args({ + name: 'status', + type: () => ServiceStatus, + description: 'Toggle SMTP', + }) + status: ServiceStatus, + ) { + const isUpdated = + await this.infraConfigService.enableAndDisableSMTP(status); + if (E.isLeft(isUpdated)) throwErr(isUpdated.left); + return true; + } + + @Mutation(() => Boolean, { + description: 'Enable or Disable User History Storing in DB', + }) + @UseGuards(GqlAuthGuard, GqlAdminGuard) + async toggleUserHistoryStore( + @Args({ + name: 'status', + type: () => ServiceStatus, + description: 'Toggle User History Store', + }) + status: ServiceStatus, + ) { + const isUpdated = await this.infraConfigService.toggleServiceStatus( + InfraConfigEnum.USER_HISTORY_STORE_ENABLED, + status, + ); + if (E.isLeft(isUpdated)) throwErr(isUpdated.left); + return true; + } } diff --git a/packages/hoppscotch-backend/src/app.module.ts b/packages/hoppscotch-backend/src/app.module.ts index d9b172ca2b..f86db38c24 100644 --- a/packages/hoppscotch-backend/src/app.module.ts +++ b/packages/hoppscotch-backend/src/app.module.ts @@ -26,6 +26,10 @@ import { loadInfraConfiguration } from './infra-config/helper'; import { MailerModule } from './mailer/mailer.module'; import { PosthogModule } from './posthog/posthog.module'; import { ScheduleModule } from '@nestjs/schedule'; +import { HealthModule } from './health/health.module'; +import { AccessTokenModule } from './access-token/access-token.module'; +import { UserLastActiveOnInterceptor } from './interceptors/user-last-active-on.interceptor'; +import { InfraTokenModule } from './infra-token/infra-token.module'; @Module({ imports: [ @@ -100,8 +104,14 @@ import { ScheduleModule } from '@nestjs/schedule'; InfraConfigModule, PosthogModule, ScheduleModule.forRoot(), + HealthModule, + AccessTokenModule, + InfraTokenModule, + ], + providers: [ + GQLComplexityPlugin, + { provide: 'APP_INTERCEPTOR', useClass: UserLastActiveOnInterceptor }, ], - providers: [GQLComplexityPlugin], controllers: [AppController], }) export class AppModule {} diff --git a/packages/hoppscotch-backend/src/auth/auth.controller.ts b/packages/hoppscotch-backend/src/auth/auth.controller.ts index 21f1fff0c8..6499854c40 100644 --- a/packages/hoppscotch-backend/src/auth/auth.controller.ts +++ b/packages/hoppscotch-backend/src/auth/auth.controller.ts @@ -7,6 +7,7 @@ import { Request, Res, UseGuards, + UseInterceptors, } from '@nestjs/common'; import { AuthService } from './auth.service'; import { SignInMagicDto } from './dto/signin-magic.dto'; @@ -28,6 +29,7 @@ import { OidcSSOGuard } from './guards/oidc.guard'; import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors'; import { ConfigService } from '@nestjs/config'; import { throwHTTPErr } from 'src/utils'; +import { UserLastLoginInterceptor } from 'src/interceptors/user-last-login.interceptor'; @UseGuards(ThrottlerBehindProxyGuard) @@ -112,6 +114,7 @@ export class AuthController { @Get('google/callback') @SkipThrottle() @UseGuards(GoogleSSOGuard) + @UseInterceptors(UserLastLoginInterceptor) async googleAuthRedirect(@Request() req, @Res() res) { const authTokens = await this.authService.generateAuthTokens(req.user.uid); if (E.isLeft(authTokens)) throwHTTPErr(authTokens.left); @@ -162,6 +165,7 @@ export class AuthController { @Get('github/callback') @SkipThrottle() @UseGuards(GithubSSOGuard) + @UseInterceptors(UserLastLoginInterceptor) async githubAuthRedirect(@Request() req, @Res() res) { const authTokens = await this.authService.generateAuthTokens(req.user.uid); if (E.isLeft(authTokens)) throwHTTPErr(authTokens.left); @@ -187,6 +191,7 @@ export class AuthController { @Get('microsoft/callback') @SkipThrottle() @UseGuards(MicrosoftSSOGuard) + @UseInterceptors(UserLastLoginInterceptor) async microsoftAuthRedirect(@Request() req, @Res() res) { const authTokens = await this.authService.generateAuthTokens(req.user.uid); if (E.isLeft(authTokens)) throwHTTPErr(authTokens.left); diff --git a/packages/hoppscotch-backend/src/auth/auth.module.ts b/packages/hoppscotch-backend/src/auth/auth.module.ts index f1a3b7d355..efb4871baf 100644 --- a/packages/hoppscotch-backend/src/auth/auth.module.ts +++ b/packages/hoppscotch-backend/src/auth/auth.module.ts @@ -14,8 +14,8 @@ import { OidcStrategy } from './strategies/oidc.strategy'; import { AuthProvider, authProviderCheck } from './helper'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { + getConfiguredSSOProvidersFromInfraConfig, isInfraConfigTablePopulated, - loadInfraConfiguration, } from 'src/infra-config/helper'; import { InfraConfigModule } from 'src/infra-config/infra-config.module'; @@ -44,8 +44,8 @@ export class AuthModule { return { module: AuthModule }; } - const env = await loadInfraConfiguration(); - const allowedAuthProviders = env.INFRA.VITE_ALLOWED_AUTH_PROVIDERS; + const allowedAuthProviders = + await getConfiguredSSOProvidersFromInfraConfig(); const providers = [ ...(authProviderCheck(AuthProvider.GOOGLE, allowedAuthProviders) diff --git a/packages/hoppscotch-backend/src/auth/auth.service.spec.ts b/packages/hoppscotch-backend/src/auth/auth.service.spec.ts index c8979518b8..91e636fc8b 100644 --- a/packages/hoppscotch-backend/src/auth/auth.service.spec.ts +++ b/packages/hoppscotch-backend/src/auth/auth.service.spec.ts @@ -9,7 +9,6 @@ import { MAGIC_LINK_EXPIRED, VERIFICATION_TOKEN_DATA_NOT_FOUND, USER_NOT_FOUND, - USERS_NOT_FOUND, } from 'src/errors'; import { MailerService } from 'src/mailer/mailer.service'; import { PrismaService } from 'src/prisma/prisma.service'; @@ -18,8 +17,6 @@ import { UserService } from 'src/user/user.service'; import { AuthService } from './auth.service'; import * as O from 'fp-ts/Option'; import { VerifyMagicDto } from './dto/verify-magic.dto'; -import { DateTime } from 'luxon'; -import * as argon2 from 'argon2'; import * as E from 'fp-ts/Either'; import { ConfigService } from '@nestjs/config'; import { InfraConfigService } from 'src/infra-config/infra-config.service'; @@ -51,6 +48,8 @@ const user: AuthUser = { photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute', isAdmin: false, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, currentGQLSession: {}, currentRESTSession: {}, @@ -172,9 +171,11 @@ describe('verifyMagicLinkTokens', () => { // generateAuthTokens mockJWT.sign.mockReturnValue(user.refreshToken); // UpdateUserRefreshToken - mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(E.right(user)); + mockUser.updateUserRefreshToken.mockResolvedValueOnce(E.right(user)); // deletePasswordlessVerificationToken mockPrisma.verificationToken.delete.mockResolvedValueOnce(passwordlessData); + // usersService.updateUserLastLoggedOn + mockUser.updateUserLastLoggedOn.mockResolvedValue(E.right(true)); const result = await authService.verifyMagicLinkTokens(magicLinkVerify); expect(result).toEqualRight({ @@ -197,9 +198,11 @@ describe('verifyMagicLinkTokens', () => { // generateAuthTokens mockJWT.sign.mockReturnValue(user.refreshToken); // UpdateUserRefreshToken - mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(E.right(user)); + mockUser.updateUserRefreshToken.mockResolvedValueOnce(E.right(user)); // deletePasswordlessVerificationToken mockPrisma.verificationToken.delete.mockResolvedValueOnce(passwordlessData); + // usersService.updateUserLastLoggedOn + mockUser.updateUserLastLoggedOn.mockResolvedValue(E.right(true)); const result = await authService.verifyMagicLinkTokens(magicLinkVerify); expect(result).toEqualRight({ @@ -239,7 +242,7 @@ describe('verifyMagicLinkTokens', () => { // generateAuthTokens mockJWT.sign.mockReturnValue(user.refreshToken); // UpdateUserRefreshToken - mockUser.UpdateUserRefreshToken.mockResolvedValueOnce( + mockUser.updateUserRefreshToken.mockResolvedValueOnce( E.left(USER_NOT_FOUND), ); @@ -264,7 +267,7 @@ describe('verifyMagicLinkTokens', () => { // generateAuthTokens mockJWT.sign.mockReturnValue(user.refreshToken); // UpdateUserRefreshToken - mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(E.right(user)); + mockUser.updateUserRefreshToken.mockResolvedValueOnce(E.right(user)); // deletePasswordlessVerificationToken mockPrisma.verificationToken.delete.mockRejectedValueOnce('RecordNotFound'); @@ -280,7 +283,7 @@ describe('generateAuthTokens', () => { test('Should successfully generate tokens with valid inputs', async () => { mockJWT.sign.mockReturnValue(user.refreshToken); // UpdateUserRefreshToken - mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(E.right(user)); + mockUser.updateUserRefreshToken.mockResolvedValueOnce(E.right(user)); const result = await authService.generateAuthTokens(user.uid); expect(result).toEqualRight({ @@ -292,7 +295,7 @@ describe('generateAuthTokens', () => { test('Should throw USER_NOT_FOUND when updating refresh tokens fails', async () => { mockJWT.sign.mockReturnValue(user.refreshToken); // UpdateUserRefreshToken - mockUser.UpdateUserRefreshToken.mockResolvedValueOnce( + mockUser.updateUserRefreshToken.mockResolvedValueOnce( E.left(USER_NOT_FOUND), ); @@ -319,7 +322,7 @@ describe('refreshAuthTokens', () => { // generateAuthTokens mockJWT.sign.mockReturnValue(user.refreshToken); // UpdateUserRefreshToken - mockUser.UpdateUserRefreshToken.mockResolvedValueOnce( + mockUser.updateUserRefreshToken.mockResolvedValueOnce( E.left(USER_NOT_FOUND), ); @@ -348,7 +351,7 @@ describe('refreshAuthTokens', () => { // generateAuthTokens mockJWT.sign.mockReturnValue('sdhjcbjsdhcbshjdcb'); // UpdateUserRefreshToken - mockUser.UpdateUserRefreshToken.mockResolvedValueOnce( + mockUser.updateUserRefreshToken.mockResolvedValueOnce( E.right({ ...user, refreshToken: 'sdhjcbjsdhcbshjdcb', diff --git a/packages/hoppscotch-backend/src/auth/auth.service.ts b/packages/hoppscotch-backend/src/auth/auth.service.ts index 9f5db2ec75..c04bdb59f8 100644 --- a/packages/hoppscotch-backend/src/auth/auth.service.ts +++ b/packages/hoppscotch-backend/src/auth/auth.service.ts @@ -112,7 +112,7 @@ export class AuthService { const refreshTokenHash = await argon2.hash(refreshToken); - const updatedUser = await this.usersService.UpdateUserRefreshToken( + const updatedUser = await this.usersService.updateUserRefreshToken( refreshTokenHash, userUid, ); @@ -320,6 +320,8 @@ export class AuthService { statusCode: HttpStatus.NOT_FOUND, }); + this.usersService.updateUserLastLoggedOn(passwordlessTokens.value.userUid); + return E.right(tokens.right); } diff --git a/packages/hoppscotch-backend/src/auth/helper.ts b/packages/hoppscotch-backend/src/auth/helper.ts index 94e06967ab..8ada704163 100644 --- a/packages/hoppscotch-backend/src/auth/helper.ts +++ b/packages/hoppscotch-backend/src/auth/helper.ts @@ -53,13 +53,13 @@ export const authCookieHandler = ( res.cookie(AuthTokenType.ACCESS_TOKEN, authTokens.access_token, { httpOnly: true, - secure: true, + secure: configService.get('ALLOW_SECURE_COOKIES') === 'true', sameSite: 'lax', maxAge: accessTokenValidity, }); res.cookie(AuthTokenType.REFRESH_TOKEN, authTokens.refresh_token, { httpOnly: true, - secure: true, + secure: configService.get('ALLOW_SECURE_COOKIES') === 'true', sameSite: 'lax', maxAge: refreshTokenValidity, }); diff --git a/packages/hoppscotch-backend/src/auth/strategies/microsoft.strategy.ts b/packages/hoppscotch-backend/src/auth/strategies/microsoft.strategy.ts index 906385f5f2..cffa680839 100644 --- a/packages/hoppscotch-backend/src/auth/strategies/microsoft.strategy.ts +++ b/packages/hoppscotch-backend/src/auth/strategies/microsoft.strategy.ts @@ -18,7 +18,7 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy) { clientID: configService.get('INFRA.MICROSOFT_CLIENT_ID'), clientSecret: configService.get('INFRA.MICROSOFT_CLIENT_SECRET'), callbackURL: configService.get('INFRA.MICROSOFT_CALLBACK_URL'), - scope: [configService.get('INFRA.MICROSOFT_SCOPE')], + scope: configService.get('INFRA.MICROSOFT_SCOPE').split(','), tenant: configService.get('INFRA.MICROSOFT_TENANT'), store: true, }); diff --git a/packages/hoppscotch-backend/src/decorators/bearer-token.decorator.ts b/packages/hoppscotch-backend/src/decorators/bearer-token.decorator.ts new file mode 100644 index 0000000000..648ed549f7 --- /dev/null +++ b/packages/hoppscotch-backend/src/decorators/bearer-token.decorator.ts @@ -0,0 +1,15 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** + ** Decorator to fetch refresh_token from cookie + */ +export const BearerToken = createParamDecorator( + (data: unknown, context: ExecutionContext) => { + const request = context.switchToHttp().getRequest(); + + // authorization token will be "Bearer " + const authorization = request.headers['authorization']; + // Remove "Bearer " and return the token only + return authorization.split(' ')[1]; + }, +); diff --git a/packages/hoppscotch-backend/src/errors.ts b/packages/hoppscotch-backend/src/errors.ts index 208a804640..5279d2bf1f 100644 --- a/packages/hoppscotch-backend/src/errors.ts +++ b/packages/hoppscotch-backend/src/errors.ts @@ -49,6 +49,18 @@ export const AUTH_PROVIDER_NOT_CONFIGURED = export const ENV_NOT_FOUND_KEY_AUTH_PROVIDERS = '"VITE_ALLOWED_AUTH_PROVIDERS" is not present in .env file'; +/** + * Environment variable "DATA_ENCRYPTION_KEY" is not present in .env file + */ +export const ENV_NOT_FOUND_KEY_DATA_ENCRYPTION_KEY = + '"DATA_ENCRYPTION_KEY" is not present in .env file'; + +/** + * Environment variable "DATA_ENCRYPTION_KEY" is changed in .env file + */ +export const ENV_INVALID_DATA_ENCRYPTION_KEY = + '"DATA_ENCRYPTION_KEY" value changed in .env file. Please undo the changes and restart the server'; + /** * Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" is empty in .env file */ @@ -480,7 +492,19 @@ export const USER_ENVIRONMENT_INVALID_ENVIRONMENT_NAME = */ export const USER_HISTORY_NOT_FOUND = 'user_history/history_not_found' as const; -/* +/** + * User history deletion failed + * (UserHistoryService) + */ +export const USER_HISTORY_DELETION_FAILED = + 'user_history/deletion_failed' as const; + +/** + * User history feature flag is disabled + * (UserHistoryService) + */ +export const USER_HISTORY_FEATURE_FLAG_DISABLED = + 'user_history/feature_flag_disabled'; /** * Invalid Request Type in History @@ -678,6 +702,19 @@ export const MAILER_SMTP_URL_UNDEFINED = 'mailer/smtp_url_undefined' as const; export const MAILER_FROM_ADDRESS_UNDEFINED = 'mailer/from_address_undefined' as const; +/** + * MAILER_SMTP_USER environment variable is not defined + * (MailerModule) + */ +export const MAILER_SMTP_USER_UNDEFINED = 'mailer/smtp_user_undefined' as const; + +/** + * MAILER_SMTP_PASSWORD environment variable is not defined + * (MailerModule) + */ +export const MAILER_SMTP_PASSWORD_UNDEFINED = + 'mailer/smtp_password_undefined' as const; + /** * SharedRequest invalid request JSON format * (ShortcodeService) @@ -761,3 +798,82 @@ export const POSTHOG_CLIENT_NOT_INITIALIZED = 'posthog/client_not_initialized'; * Inputs supplied are invalid */ export const INVALID_PARAMS = 'invalid_parameters' as const; + +/** + * The provided label for the access-token is short (less than 3 characters) + * (AccessTokenService) + */ +export const ACCESS_TOKEN_LABEL_SHORT = 'access_token/label_too_short'; + +/** + * The provided expiryInDays value is not valid + * (AccessTokenService) + */ +export const ACCESS_TOKEN_EXPIRY_INVALID = 'access_token/expiry_days_invalid'; + +/** + * The provided PAT ID is invalid + * (AccessTokenService) + */ +export const ACCESS_TOKEN_NOT_FOUND = 'access_token/access_token_not_found'; + +/** + * AccessTokens is expired + * (AccessTokenService) + */ +export const ACCESS_TOKEN_EXPIRED = 'TOKEN_EXPIRED'; + +/** + * AccessTokens is invalid + * (AccessTokenService) + */ +export const ACCESS_TOKEN_INVALID = 'TOKEN_INVALID'; + +/** + * AccessTokens is invalid + * (AccessTokenService) + */ +export const ACCESS_TOKENS_INVALID_DATA_ID = 'INVALID_ID'; + +/** + * The provided label for the infra-token is short (less than 3 characters) + * (InfraTokenService) + */ +export const INFRA_TOKEN_LABEL_SHORT = 'infra_token/label_too_short'; + +/** + * The provided expiryInDays value is not valid + * (InfraTokenService) + */ +export const INFRA_TOKEN_EXPIRY_INVALID = 'infra_token/expiry_days_invalid'; + +/** + * The provided Infra Token ID is invalid + * (InfraTokenService) + */ +export const INFRA_TOKEN_NOT_FOUND = 'infra_token/infra_token_not_found'; + +/** + * Authorization missing in header (Check 'Authorization' Header) + * (InfraTokenGuard) + */ +export const INFRA_TOKEN_HEADER_MISSING = + 'infra_token/authorization_token_missing'; + +/** + * Infra Token is invalid + * (InfraTokenGuard) + */ +export const INFRA_TOKEN_INVALID_TOKEN = 'infra_token/invalid_token'; + +/** + * Infra Token is expired + * (InfraTokenGuard) + */ +export const INFRA_TOKEN_EXPIRED = 'infra_token/expired'; + +/** + * Token creator not found + * (InfraTokenService) + */ +export const INFRA_TOKEN_CREATOR_NOT_FOUND = 'infra_token/creator_not_found'; diff --git a/packages/hoppscotch-backend/src/gql-schema.ts b/packages/hoppscotch-backend/src/gql-schema.ts index bd2434e2fa..87d6dffd05 100644 --- a/packages/hoppscotch-backend/src/gql-schema.ts +++ b/packages/hoppscotch-backend/src/gql-schema.ts @@ -28,6 +28,8 @@ import { UserEnvsUserResolver } from './user-environment/user.resolver'; import { UserHistoryUserResolver } from './user-history/user.resolver'; import { UserSettingsUserResolver } from './user-settings/user.resolver'; import { InfraResolver } from './admin/infra.resolver'; +import { InfraConfigResolver } from './infra-config/infra-config.resolver'; +import { InfraTokenResolver } from './infra-token/infra-token.resolver'; /** * All the resolvers present in the application. @@ -58,6 +60,8 @@ const RESOLVERS = [ UserRequestUserCollectionResolver, UserSettingsResolver, UserSettingsUserResolver, + InfraConfigResolver, + InfraTokenResolver, ]; /** diff --git a/packages/hoppscotch-backend/src/guards/infra-token.guard.ts b/packages/hoppscotch-backend/src/guards/infra-token.guard.ts new file mode 100644 index 0000000000..e627b3fcbb --- /dev/null +++ b/packages/hoppscotch-backend/src/guards/infra-token.guard.ts @@ -0,0 +1,47 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { DateTime } from 'luxon'; +import { + INFRA_TOKEN_EXPIRED, + INFRA_TOKEN_HEADER_MISSING, + INFRA_TOKEN_INVALID_TOKEN, +} from 'src/errors'; + +@Injectable() +export class InfraTokenGuard implements CanActivate { + constructor(private readonly prisma: PrismaService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const authorization = request.headers['authorization']; + + if (!authorization) + throw new UnauthorizedException(INFRA_TOKEN_HEADER_MISSING); + + if (!authorization.startsWith('Bearer ')) + throw new UnauthorizedException(INFRA_TOKEN_INVALID_TOKEN); + + const token = authorization.split(' ')[1]; + + if (!token) throw new UnauthorizedException(INFRA_TOKEN_INVALID_TOKEN); + + const infraToken = await this.prisma.infraToken.findUnique({ + where: { token }, + }); + + if (infraToken === null) + throw new UnauthorizedException(INFRA_TOKEN_INVALID_TOKEN); + + const currentTime = DateTime.now().toISO(); + if (currentTime > infraToken.expiresOn?.toISOString()) { + throw new UnauthorizedException(INFRA_TOKEN_EXPIRED); + } + + return true; + } +} diff --git a/packages/hoppscotch-backend/src/guards/rest-pat-auth.guard.ts b/packages/hoppscotch-backend/src/guards/rest-pat-auth.guard.ts new file mode 100644 index 0000000000..d59e77af61 --- /dev/null +++ b/packages/hoppscotch-backend/src/guards/rest-pat-auth.guard.ts @@ -0,0 +1,48 @@ +import { + BadRequestException, + CanActivate, + ExecutionContext, + Injectable, +} from '@nestjs/common'; +import { Request } from 'express'; +import { AccessTokenService } from 'src/access-token/access-token.service'; +import * as E from 'fp-ts/Either'; +import { DateTime } from 'luxon'; +import { ACCESS_TOKEN_EXPIRED, ACCESS_TOKEN_INVALID } from 'src/errors'; +import { createCLIErrorResponse } from 'src/access-token/helper'; +@Injectable() +export class PATAuthGuard implements CanActivate { + constructor(private accessTokenService: AccessTokenService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const token = this.extractTokenFromHeader(request); + if (!token) { + throw new BadRequestException( + createCLIErrorResponse(ACCESS_TOKEN_INVALID), + ); + } + + const userAccessToken = await this.accessTokenService.getUserPAT(token); + if (E.isLeft(userAccessToken)) + throw new BadRequestException( + createCLIErrorResponse(ACCESS_TOKEN_INVALID), + ); + request.user = userAccessToken.right.user; + + const accessToken = userAccessToken.right; + if (accessToken.expiresOn === null) return true; + + const today = DateTime.now().toISO(); + if (accessToken.expiresOn.toISOString() > today) return true; + + throw new BadRequestException( + createCLIErrorResponse(ACCESS_TOKEN_EXPIRED), + ); + } + + private extractTokenFromHeader(request: Request): string | undefined { + const [type, token] = request.headers.authorization?.split(' ') ?? []; + return type === 'Bearer' ? token : undefined; + } +} diff --git a/packages/hoppscotch-backend/src/health/health.controller.ts b/packages/hoppscotch-backend/src/health/health.controller.ts new file mode 100644 index 0000000000..0f63261152 --- /dev/null +++ b/packages/hoppscotch-backend/src/health/health.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Get } from '@nestjs/common'; +import { + HealthCheck, + HealthCheckService, + PrismaHealthIndicator, +} from '@nestjs/terminus'; +import { PrismaService } from 'src/prisma/prisma.service'; + +@Controller('health') +export class HealthController { + constructor( + private health: HealthCheckService, + private prismaHealth: PrismaHealthIndicator, + private prisma: PrismaService, + ) {} + + @Get() + @HealthCheck() + check() { + return this.health.check([ + async () => this.prismaHealth.pingCheck('database', this.prisma), + ]); + } +} diff --git a/packages/hoppscotch-backend/src/health/health.module.ts b/packages/hoppscotch-backend/src/health/health.module.ts new file mode 100644 index 0000000000..716456440b --- /dev/null +++ b/packages/hoppscotch-backend/src/health/health.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { HealthController } from './health.controller'; +import { PrismaModule } from 'src/prisma/prisma.module'; +import { TerminusModule } from '@nestjs/terminus'; + +@Module({ + imports: [PrismaModule, TerminusModule], + controllers: [HealthController], +}) +export class HealthModule {} diff --git a/packages/hoppscotch-backend/src/infra-config/helper.ts b/packages/hoppscotch-backend/src/infra-config/helper.ts index 5e09087260..74d1174f50 100644 --- a/packages/hoppscotch-backend/src/infra-config/helper.ts +++ b/packages/hoppscotch-backend/src/infra-config/helper.ts @@ -2,17 +2,26 @@ import { AuthProvider } from 'src/auth/helper'; import { AUTH_PROVIDER_NOT_CONFIGURED, DATABASE_TABLE_NOT_EXIST, + ENV_INVALID_DATA_ENCRYPTION_KEY, } from 'src/errors'; import { PrismaService } from 'src/prisma/prisma.service'; import { InfraConfigEnum } from 'src/types/InfraConfig'; -import { throwErr } from 'src/utils'; +import { decrypt, encrypt, throwErr } from 'src/utils'; import { randomBytes } from 'crypto'; +import { InfraConfig } from '@prisma/client'; export enum ServiceStatus { ENABLE = 'ENABLE', DISABLE = 'DISABLE', } +type DefaultInfraConfig = { + name: InfraConfigEnum; + value: string; + lastSyncedEnvFileValue: string; + isEncrypted: boolean; +}; + const AuthProviderConfigurations = { [AuthProvider.GOOGLE]: [ InfraConfigEnum.GOOGLE_CLIENT_ID, @@ -33,10 +42,18 @@ const AuthProviderConfigurations = { InfraConfigEnum.MICROSOFT_SCOPE, InfraConfigEnum.MICROSOFT_TENANT, ], - [AuthProvider.EMAIL]: [ - InfraConfigEnum.MAILER_SMTP_URL, - InfraConfigEnum.MAILER_ADDRESS_FROM, - ], + [AuthProvider.EMAIL]: + process.env.MAILER_USE_CUSTOM_CONFIGS === 'true' + ? [ + InfraConfigEnum.MAILER_SMTP_HOST, + InfraConfigEnum.MAILER_SMTP_PORT, + InfraConfigEnum.MAILER_SMTP_SECURE, + InfraConfigEnum.MAILER_SMTP_USER, + InfraConfigEnum.MAILER_SMTP_PASSWORD, + InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED, + InfraConfigEnum.MAILER_ADDRESS_FROM, + ] + : [InfraConfigEnum.MAILER_SMTP_URL, InfraConfigEnum.MAILER_ADDRESS_FROM], [AuthProvider.OIDC]: [ InfraConfigEnum.OIDC_CLIENT_ID, InfraConfigEnum.OIDC_CLIENT_SECRET, @@ -57,11 +74,18 @@ export async function loadInfraConfiguration() { let environmentObject: Record = {}; infraConfigs.forEach((infraConfig) => { - environmentObject[infraConfig.name] = infraConfig.value; + if (infraConfig.isEncrypted) { + environmentObject[infraConfig.name] = decrypt(infraConfig.value); + } else { + environmentObject[infraConfig.name] = infraConfig.value; + } }); return { INFRA: environmentObject }; } catch (error) { + if (error.code === 'ERR_OSSL_BAD_DECRYPT') + throw new Error(ENV_INVALID_DATA_ENCRYPTION_KEY); + // Prisma throw error if 'Can't reach at database server' OR 'Table does not exist' // Reason for not throwing error is, we want successful build during 'postinstall' and generate dist files return { INFRA: {} }; @@ -72,139 +96,325 @@ export async function loadInfraConfiguration() { * Read the default values from .env file and return them as an array * @returns Array of default infra configs */ -export async function getDefaultInfraConfigs(): Promise< - { name: InfraConfigEnum; value: string }[] -> { +export async function getDefaultInfraConfigs(): Promise { const prisma = new PrismaService(); // Prepare rows for 'infra_config' table with default values (from .env) for each 'name' - const infraConfigDefaultObjs: { name: InfraConfigEnum; value: string }[] = [ + const configuredSSOProviders = getConfiguredSSOProvidersFromEnvFile(); + const generatedAnalyticsUserId = generateAnalyticsUserId(); + + const infraConfigDefaultObjs: DefaultInfraConfig[] = [ + { + name: InfraConfigEnum.MAILER_SMTP_ENABLE, + value: process.env.MAILER_SMTP_ENABLE ?? 'true', + lastSyncedEnvFileValue: process.env.MAILER_SMTP_ENABLE ?? 'true', + isEncrypted: false, + }, + { + name: InfraConfigEnum.MAILER_USE_CUSTOM_CONFIGS, + value: process.env.MAILER_USE_CUSTOM_CONFIGS ?? 'false', + lastSyncedEnvFileValue: process.env.MAILER_USE_CUSTOM_CONFIGS ?? 'false', + isEncrypted: false, + }, { name: InfraConfigEnum.MAILER_SMTP_URL, - value: process.env.MAILER_SMTP_URL, + value: encrypt(process.env.MAILER_SMTP_URL), + lastSyncedEnvFileValue: encrypt(process.env.MAILER_SMTP_URL), + isEncrypted: true, }, { name: InfraConfigEnum.MAILER_ADDRESS_FROM, value: process.env.MAILER_ADDRESS_FROM, + lastSyncedEnvFileValue: process.env.MAILER_ADDRESS_FROM, + isEncrypted: false, + }, + { + name: InfraConfigEnum.MAILER_SMTP_HOST, + value: process.env.MAILER_SMTP_HOST, + lastSyncedEnvFileValue: process.env.MAILER_SMTP_HOST, + isEncrypted: false, + }, + { + name: InfraConfigEnum.MAILER_SMTP_PORT, + value: process.env.MAILER_SMTP_PORT, + lastSyncedEnvFileValue: process.env.MAILER_SMTP_PORT, + isEncrypted: false, + }, + { + name: InfraConfigEnum.MAILER_SMTP_SECURE, + value: process.env.MAILER_SMTP_SECURE, + lastSyncedEnvFileValue: process.env.MAILER_SMTP_SECURE, + isEncrypted: false, + }, + { + name: InfraConfigEnum.MAILER_SMTP_USER, + value: process.env.MAILER_SMTP_USER, + lastSyncedEnvFileValue: process.env.MAILER_SMTP_USER, + isEncrypted: false, + }, + { + name: InfraConfigEnum.MAILER_SMTP_PASSWORD, + value: encrypt(process.env.MAILER_SMTP_PASSWORD), + lastSyncedEnvFileValue: encrypt(process.env.MAILER_SMTP_PASSWORD), + isEncrypted: true, + }, + { + name: InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED, + value: process.env.MAILER_TLS_REJECT_UNAUTHORIZED, + lastSyncedEnvFileValue: process.env.MAILER_TLS_REJECT_UNAUTHORIZED, + isEncrypted: false, }, { name: InfraConfigEnum.GOOGLE_CLIENT_ID, - value: process.env.GOOGLE_CLIENT_ID, + value: encrypt(process.env.GOOGLE_CLIENT_ID), + lastSyncedEnvFileValue: encrypt(process.env.GOOGLE_CLIENT_ID), + isEncrypted: true, }, { name: InfraConfigEnum.GOOGLE_CLIENT_SECRET, - value: process.env.GOOGLE_CLIENT_SECRET, + value: encrypt(process.env.GOOGLE_CLIENT_SECRET), + lastSyncedEnvFileValue: encrypt(process.env.GOOGLE_CLIENT_SECRET), + isEncrypted: true, }, { name: InfraConfigEnum.GOOGLE_CALLBACK_URL, value: process.env.GOOGLE_CALLBACK_URL, + lastSyncedEnvFileValue: process.env.GOOGLE_CALLBACK_URL, + isEncrypted: false, }, { name: InfraConfigEnum.GOOGLE_SCOPE, value: process.env.GOOGLE_SCOPE, + lastSyncedEnvFileValue: process.env.GOOGLE_SCOPE, + isEncrypted: false, }, { name: InfraConfigEnum.GITHUB_CLIENT_ID, - value: process.env.GITHUB_CLIENT_ID, + value: encrypt(process.env.GITHUB_CLIENT_ID), + lastSyncedEnvFileValue: encrypt(process.env.GITHUB_CLIENT_ID), + isEncrypted: true, }, { name: InfraConfigEnum.GITHUB_CLIENT_SECRET, - value: process.env.GITHUB_CLIENT_SECRET, + value: encrypt(process.env.GITHUB_CLIENT_SECRET), + lastSyncedEnvFileValue: encrypt(process.env.GITHUB_CLIENT_SECRET), + isEncrypted: true, }, { name: InfraConfigEnum.GITHUB_CALLBACK_URL, value: process.env.GITHUB_CALLBACK_URL, + lastSyncedEnvFileValue: process.env.GITHUB_CALLBACK_URL, + isEncrypted: false, }, { name: InfraConfigEnum.GITHUB_SCOPE, value: process.env.GITHUB_SCOPE, + lastSyncedEnvFileValue: process.env.GITHUB_SCOPE, + isEncrypted: false, }, { name: InfraConfigEnum.MICROSOFT_CLIENT_ID, - value: process.env.MICROSOFT_CLIENT_ID, + value: encrypt(process.env.MICROSOFT_CLIENT_ID), + lastSyncedEnvFileValue: encrypt(process.env.MICROSOFT_CLIENT_ID), + isEncrypted: true, }, { name: InfraConfigEnum.MICROSOFT_CLIENT_SECRET, - value: process.env.MICROSOFT_CLIENT_SECRET, + value: encrypt(process.env.MICROSOFT_CLIENT_SECRET), + lastSyncedEnvFileValue: encrypt(process.env.MICROSOFT_CLIENT_SECRET), + isEncrypted: true, }, { name: InfraConfigEnum.MICROSOFT_CALLBACK_URL, value: process.env.MICROSOFT_CALLBACK_URL, + lastSyncedEnvFileValue: process.env.MICROSOFT_CALLBACK_URL, + isEncrypted: false, }, { name: InfraConfigEnum.MICROSOFT_SCOPE, value: process.env.MICROSOFT_SCOPE, + lastSyncedEnvFileValue: process.env.MICROSOFT_SCOPE, + isEncrypted: false, }, { name: InfraConfigEnum.MICROSOFT_TENANT, value: process.env.MICROSOFT_TENANT, + lastSyncedEnvFileValue: process.env.MICROSOFT_TENANT, + isEncrypted: false, }, { name: InfraConfigEnum.OIDC_CLIENT_ID, - value: process.env.OIDC_CLIENT_ID, + value: encrypt(process.env.OIDC_CLIENT_ID), + lastSyncedEnvFileValue: encrypt(process.env.OIDC_CLIENT_ID), + isEncrypted: true, }, { name: InfraConfigEnum.OIDC_CLIENT_SECRET, - value: process.env.OIDC_CLIENT_SECRET, + value: encrypt(process.env.OIDC_CLIENT_SECRET), + lastSyncedEnvFileValue: encrypt(process.env.OIDC_CLIENT_SECRET), + isEncrypted: true, }, { name: InfraConfigEnum.OIDC_CALLBACK_URL, value: process.env.OIDC_CALLBACK_URL, + lastSyncedEnvFileValue: process.env.OIDC_CALLBACK_URL, + isEncrypted: false, }, { name: InfraConfigEnum.OIDC_SCOPE, value: process.env.OIDC_SCOPE, + lastSyncedEnvFileValue: process.env.OIDC_SCOPE, + isEncrypted: false, }, { name: InfraConfigEnum.OIDC_ISSUER, value: process.env.OIDC_ISSUER, + lastSyncedEnvFileValue: process.env.OIDC_ISSUER, + isEncrypted: false, }, { name: InfraConfigEnum.OIDC_AUTH_URL, value: process.env.OIDC_AUTH_URL, + lastSyncedEnvFileValue: process.env.OIDC_AUTH_URL, + isEncrypted: false, }, { name: InfraConfigEnum.OIDC_TOKEN_URL, value: process.env.OIDC_TOKEN_URL, + lastSyncedEnvFileValue: process.env.OIDC_TOKEN_URL, + isEncrypted: false, }, { name: InfraConfigEnum.OIDC_USERINFO_URL, value: process.env.OIDC_USERINFO_URL, + lastSyncedEnvFileValue: process.env.OIDC_USERINFO_URL, + isEncrypted: false, }, { name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS, - value: getConfiguredSSOProviders(), + value: configuredSSOProviders, + lastSyncedEnvFileValue: configuredSSOProviders, + isEncrypted: false, }, { name: InfraConfigEnum.ALLOW_ANALYTICS_COLLECTION, value: false.toString(), + lastSyncedEnvFileValue: null, + isEncrypted: false, }, { name: InfraConfigEnum.ANALYTICS_USER_ID, - value: generateAnalyticsUserId(), + value: generatedAnalyticsUserId, + lastSyncedEnvFileValue: null, + isEncrypted: false, }, { name: InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP, value: (await prisma.infraConfig.count()) === 0 ? 'true' : 'false', + lastSyncedEnvFileValue: null, + isEncrypted: false, + }, + { + name: InfraConfigEnum.USER_HISTORY_STORE_ENABLED, + value: 'true', + lastSyncedEnvFileValue: null, + isEncrypted: false, }, ]; return infraConfigDefaultObjs; } +/** + * Get the missing entries in the 'infra_config' table + * @returns Array of InfraConfig + */ +export async function getMissingInfraConfigEntries( + infraConfigDefaultObjs: DefaultInfraConfig[], +) { + const prisma = new PrismaService(); + const dbInfraConfigs = await prisma.infraConfig.findMany(); + + const missingEntries = infraConfigDefaultObjs.filter( + (config) => + !dbInfraConfigs.some((dbConfig) => dbConfig.name === config.name), + ); + + return missingEntries; +} + +/** + * Get the encryption required entries in the 'infra_config' table + * @returns Array of InfraConfig + */ +export async function getEncryptionRequiredInfraConfigEntries( + infraConfigDefaultObjs: DefaultInfraConfig[], +) { + const prisma = new PrismaService(); + const dbInfraConfigs = await prisma.infraConfig.findMany(); + + const requiredEncryption = dbInfraConfigs.filter((dbConfig) => { + const defaultConfig = infraConfigDefaultObjs.find( + (config) => config.name === dbConfig.name, + ); + if (!defaultConfig) return false; + return defaultConfig.isEncrypted !== dbConfig.isEncrypted; + }); + + return requiredEncryption; +} + +/** + * Sync the 'infra_config' table with .env file + * @returns Array of InfraConfig + */ +export async function syncInfraConfigWithEnvFile() { + const prisma = new PrismaService(); + const dbInfraConfigs = await prisma.infraConfig.findMany(); + + const updateRequiredObjs: (Partial & { id: string })[] = []; + + for (const dbConfig of dbInfraConfigs) { + let envValue = process.env[dbConfig.name]; + + // lastSyncedEnvFileValue null check for backward compatibility from 2024.10.2 and below + if (!dbConfig.lastSyncedEnvFileValue && envValue) { + const configValue = dbConfig.isEncrypted ? encrypt(envValue) : envValue; + updateRequiredObjs.push({ + id: dbConfig.id, + value: dbConfig.value === null ? configValue : undefined, + lastSyncedEnvFileValue: configValue, + }); + continue; + } + + // If the value in the database is different from the value in the .env file, means the value in the .env file has been updated + const rawLastSyncedEnvFileValue = dbConfig.isEncrypted + ? decrypt(dbConfig.lastSyncedEnvFileValue) + : dbConfig.lastSyncedEnvFileValue; + + if (rawLastSyncedEnvFileValue != envValue) { + const configValue = dbConfig.isEncrypted ? encrypt(envValue) : envValue; + updateRequiredObjs.push({ + id: dbConfig.id, + value: configValue ?? null, + lastSyncedEnvFileValue: configValue ?? null, + }); + } + } + + return updateRequiredObjs; +} + /** * Verify if 'infra_config' table is loaded with all entries * @returns boolean */ export async function isInfraConfigTablePopulated(): Promise { - const prisma = new PrismaService(); try { - const dbInfraConfigs = await prisma.infraConfig.findMany(); - const infraConfigDefaultObjs = await getDefaultInfraConfigs(); - - const propsRemainingToInsert = infraConfigDefaultObjs.filter( - (p) => !dbInfraConfigs.find((e) => e.name === p.name), - ); + const defaultInfraConfigs = await getDefaultInfraConfigs(); + const propsRemainingToInsert = + await getMissingInfraConfigEntries(defaultInfraConfigs); if (propsRemainingToInsert.length > 0) { console.log( @@ -233,10 +443,11 @@ export function stopApp() { } /** - * Get the configured SSO providers + * Get the configured SSO providers from .env file + * @description This function verify if the required parameters for each SSO provider are configured in .env file. Usage on first time setup and reset. * @returns Array of configured SSO providers */ -export function getConfiguredSSOProviders() { +export function getConfiguredSSOProvidersFromEnvFile() { const allowedAuthProviders: string[] = process.env.VITE_ALLOWED_AUTH_PROVIDERS.split(','); let configuredAuthProviders: string[] = []; @@ -247,7 +458,6 @@ export function getConfiguredSSOProviders() { const isConfigured = configParameters.every((configParameter) => { return process.env[configParameter]; }); - if (isConfigured) configuredAuthProviders.push(provider); }; @@ -264,7 +474,47 @@ export function getConfiguredSSOProviders() { console.log( `${unConfiguredAuthProviders.join( ',', - )} SSO auth provider(s) are not configured properly. Do configure them from Admin Dashboard.`, + )} SSO auth provider(s) are not configured properly in .env file. Do configure them from Admin Dashboard.`, + ); + } + + return configuredAuthProviders.join(','); +} + +/** + * Get the configured SSO providers from 'infra_config' table. + * @description Usage every time the app starts by AuthModule to initiate Strategies. + * @returns Array of configured SSO providers + */ +export async function getConfiguredSSOProvidersFromInfraConfig() { + const env = await loadInfraConfiguration(); + + const allowedAuthProviders: string[] = + env['INFRA'].VITE_ALLOWED_AUTH_PROVIDERS.split(','); + let configuredAuthProviders: string[] = []; + + const addProviderIfConfigured = (provider) => { + const configParameters: string[] = AuthProviderConfigurations[provider]; + + const isConfigured = configParameters.every((configParameter) => { + return env['INFRA'][configParameter]; + }); + if (isConfigured) configuredAuthProviders.push(provider); + }; + + allowedAuthProviders.forEach((provider) => addProviderIfConfigured(provider)); + + if (configuredAuthProviders.length === 0) { + return ''; + } else if (allowedAuthProviders.length !== configuredAuthProviders.length) { + const prisma = new PrismaService(); + await prisma.infraConfig.update({ + where: { name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS }, + data: { value: configuredAuthProviders.join(',') }, + }); + stopApp(); + console.log( + `${configuredAuthProviders.join(',')} SSO auth provider(s) are configured properly. To enable other SSO providers, configure them from Admin Dashboard.`, ); } diff --git a/packages/hoppscotch-backend/src/infra-config/infra-config.module.ts b/packages/hoppscotch-backend/src/infra-config/infra-config.module.ts index cfb0ee3a2d..521f10c967 100644 --- a/packages/hoppscotch-backend/src/infra-config/infra-config.module.ts +++ b/packages/hoppscotch-backend/src/infra-config/infra-config.module.ts @@ -2,10 +2,12 @@ import { Module } from '@nestjs/common'; import { InfraConfigService } from './infra-config.service'; import { PrismaModule } from 'src/prisma/prisma.module'; import { SiteController } from './infra-config.controller'; +import { InfraConfigResolver } from './infra-config.resolver'; +import { PubSubModule } from 'src/pubsub/pubsub.module'; @Module({ - imports: [PrismaModule], - providers: [InfraConfigService], + imports: [PrismaModule, PubSubModule], + providers: [InfraConfigResolver, InfraConfigService], exports: [InfraConfigService], controllers: [SiteController], }) diff --git a/packages/hoppscotch-backend/src/infra-config/infra-config.resolver.ts b/packages/hoppscotch-backend/src/infra-config/infra-config.resolver.ts new file mode 100644 index 0000000000..aeb08cd3e4 --- /dev/null +++ b/packages/hoppscotch-backend/src/infra-config/infra-config.resolver.ts @@ -0,0 +1,59 @@ +import { UseGuards } from '@nestjs/common'; +import { Args, Query, Resolver, Subscription } from '@nestjs/graphql'; +import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard'; +import { InfraConfig } from './infra-config.model'; +import { InfraConfigService } from './infra-config.service'; +import { GqlAuthGuard } from 'src/guards/gql-auth.guard'; +import { SkipThrottle } from '@nestjs/throttler'; +import { PubSubService } from 'src/pubsub/pubsub.service'; +import { InfraConfigEnum } from 'src/types/InfraConfig'; +import * as E from 'fp-ts/Either'; +import { throwErr } from 'src/utils'; + +@UseGuards(GqlThrottlerGuard) +@Resolver(() => InfraConfig) +export class InfraConfigResolver { + constructor( + private infraConfigService: InfraConfigService, + private pubsub: PubSubService, + ) {} + + /* Query */ + + @Query(() => Boolean, { + description: 'Check if the SMTP is enabled or not', + }) + @UseGuards(GqlAuthGuard) + isSMTPEnabled() { + return this.infraConfigService.isSMTPEnabled(); + } + + @Query(() => InfraConfig, { + description: 'Check if user history is enabled or not', + }) + @UseGuards(GqlAuthGuard) + async isUserHistoryEnabled() { + const isEnabled = await this.infraConfigService.isUserHistoryEnabled(); + if (E.isLeft(isEnabled)) throwErr(isEnabled.left); + return isEnabled.right; + } + + /* Subscriptions */ + + @Subscription(() => String, { + description: 'Subscription for infra config update', + resolve: (value) => value, + }) + @SkipThrottle() + @UseGuards(GqlAuthGuard) + infraConfigUpdate( + @Args({ + name: 'configName', + description: 'Infra config key', + type: () => InfraConfigEnum, + }) + configName: string, + ) { + return this.pubsub.asyncIterator(`infra_config/${configName}/updated`); + } +} diff --git a/packages/hoppscotch-backend/src/infra-config/infra-config.service.spec.ts b/packages/hoppscotch-backend/src/infra-config/infra-config.service.spec.ts index 7072942318..f1c65f709c 100644 --- a/packages/hoppscotch-backend/src/infra-config/infra-config.service.spec.ts +++ b/packages/hoppscotch-backend/src/infra-config/infra-config.service.spec.ts @@ -11,15 +11,20 @@ import { ConfigService } from '@nestjs/config'; import * as helper from './helper'; import { InfraConfig as dbInfraConfig } from '@prisma/client'; import { InfraConfig } from './infra-config.model'; +import { PubSubService } from 'src/pubsub/pubsub.service'; +import { ServiceStatus } from './helper'; +import * as E from 'fp-ts/Either'; const mockPrisma = mockDeep(); const mockConfigService = mockDeep(); +const mockPubsub = mockDeep(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const infraConfigService = new InfraConfigService( mockPrisma, mockConfigService, + mockPubsub, ); const INITIALIZED_DATE_CONST = new Date(); @@ -28,7 +33,8 @@ const dbInfraConfigs: dbInfraConfig[] = [ id: '3', name: InfraConfigEnum.GOOGLE_CLIENT_ID, value: 'abcdefghijkl', - active: true, + lastSyncedEnvFileValue: 'abcdefghijkl', + isEncrypted: false, createdOn: INITIALIZED_DATE_CONST, updatedOn: INITIALIZED_DATE_CONST, }, @@ -36,7 +42,8 @@ const dbInfraConfigs: dbInfraConfig[] = [ id: '4', name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS, value: 'google', - active: true, + lastSyncedEnvFileValue: 'google', + isEncrypted: false, createdOn: INITIALIZED_DATE_CONST, updatedOn: INITIALIZED_DATE_CONST, }, @@ -62,11 +69,16 @@ describe('InfraConfigService', () => { const name = InfraConfigEnum.GOOGLE_CLIENT_ID; const value = 'true'; + // @ts-ignore + mockPrisma.infraConfig.findUnique.mockResolvedValueOnce({ + isEncrypted: false, + }); mockPrisma.infraConfig.update.mockResolvedValueOnce({ id: '', name, value, - active: true, + lastSyncedEnvFileValue: value, + isEncrypted: false, createdOn: new Date(), updatedOn: new Date(), }); @@ -82,11 +94,16 @@ describe('InfraConfigService', () => { const name = InfraConfigEnum.GOOGLE_CLIENT_ID; const value = 'true'; + // @ts-ignore + mockPrisma.infraConfig.findUnique.mockResolvedValueOnce({ + isEncrypted: false, + }); mockPrisma.infraConfig.update.mockResolvedValueOnce({ id: '', name, value, - active: true, + lastSyncedEnvFileValue: value, + isEncrypted: false, createdOn: new Date(), updatedOn: new Date(), }); @@ -102,11 +119,16 @@ describe('InfraConfigService', () => { const name = InfraConfigEnum.GOOGLE_CLIENT_ID; const value = 'true'; + // @ts-ignore + mockPrisma.infraConfig.findUnique.mockResolvedValueOnce({ + isEncrypted: false, + }); mockPrisma.infraConfig.update.mockResolvedValueOnce({ id: '', name, value, - active: true, + lastSyncedEnvFileValue: value, + isEncrypted: false, createdOn: new Date(), updatedOn: new Date(), }); @@ -120,6 +142,11 @@ describe('InfraConfigService', () => { const name = InfraConfigEnum.GOOGLE_CLIENT_ID; const value = 'true'; + // @ts-ignore + mockPrisma.infraConfig.findUnique.mockResolvedValueOnce({ + isEncrypted: false, + }); + jest.spyOn(helper, 'stopApp').mockReturnValueOnce(); await infraConfigService.update(name, value); @@ -151,7 +178,8 @@ describe('InfraConfigService', () => { id: '', name, value, - active: true, + lastSyncedEnvFileValue: value, + isEncrypted: false, createdOn: new Date(), updatedOn: new Date(), }); @@ -220,4 +248,59 @@ describe('InfraConfigService', () => { ); }); }); + + describe('toggleServiceStatus', () => { + it('should toggle the service status', async () => { + const configName = infraConfigs[0].name; + const configStatus = ServiceStatus.DISABLE; + + jest + .spyOn(infraConfigService, 'update') + .mockResolvedValueOnce( + E.right({ name: configName, value: configStatus }), + ); + + expect( + await infraConfigService.toggleServiceStatus(configName, configStatus), + ).toEqualRight(true); + }); + it('should publish the updated config value', async () => { + const configName = infraConfigs[0].name; + const configStatus = ServiceStatus.DISABLE; + + jest + .spyOn(infraConfigService, 'update') + .mockResolvedValueOnce( + E.right({ name: configName, value: configStatus }), + ); + + await infraConfigService.toggleServiceStatus(configName, configStatus); + + expect(mockPubsub.publish).toHaveBeenCalledTimes(1); + expect(mockPubsub.publish).toHaveBeenCalledWith( + 'infra_config/GOOGLE_CLIENT_ID/updated', + configStatus, + ); + }); + }); + + describe('isUserHistoryEnabled', () => { + it('should return true if the user history is enabled', async () => { + const response = { + name: InfraConfigEnum.USER_HISTORY_STORE_ENABLED, + value: ServiceStatus.ENABLE, + }; + + jest.spyOn(infraConfigService, 'get').mockResolvedValueOnce( + E.right({ + name: InfraConfigEnum.USER_HISTORY_STORE_ENABLED, + value: ServiceStatus.ENABLE, + }), + ); + + expect(await infraConfigService.isUserHistoryEnabled()).toEqualRight( + response, + ); + }); + }); }); diff --git a/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts b/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts index 0e76d74525..963b3404ea 100644 --- a/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts +++ b/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts @@ -15,21 +15,32 @@ import { INFRA_CONFIG_OPERATION_NOT_ALLOWED, } from 'src/errors'; import { + decrypt, + encrypt, throwErr, validateSMTPEmail, validateSMTPUrl, validateUrl, } from 'src/utils'; import { ConfigService } from '@nestjs/config'; -import { ServiceStatus, getDefaultInfraConfigs, stopApp } from './helper'; +import { + ServiceStatus, + getDefaultInfraConfigs, + getEncryptionRequiredInfraConfigEntries, + getMissingInfraConfigEntries, + stopApp, + syncInfraConfigWithEnvFile, +} from './helper'; import { EnableAndDisableSSOArgs, InfraConfigArgs } from './input-args'; import { AuthProvider } from 'src/auth/helper'; +import { PubSubService } from 'src/pubsub/pubsub.service'; @Injectable() export class InfraConfigService implements OnModuleInit { constructor( private readonly prisma: PrismaService, private readonly configService: ConfigService, + private readonly pubsub: PubSubService, ) {} // Following fields are not updatable by `infraConfigs` Mutation. Use dedicated mutations for these fields instead. @@ -38,6 +49,8 @@ export class InfraConfigService implements OnModuleInit { InfraConfigEnum.ALLOW_ANALYTICS_COLLECTION, InfraConfigEnum.ANALYTICS_USER_ID, InfraConfigEnum.IS_FIRST_TIME_INFRA_SETUP, + InfraConfigEnum.MAILER_SMTP_ENABLE, + InfraConfigEnum.USER_HISTORY_STORE_ENABLED, ]; // Following fields can not be fetched by `infraConfigs` Query. Use dedicated queries for these fields instead. EXCLUDE_FROM_FETCH_CONFIGS = [ @@ -56,17 +69,50 @@ export class InfraConfigService implements OnModuleInit { */ async initializeInfraConfigTable() { try { - // Fetch the default values (value in .env) for configs to be saved in 'infra_config' table - const infraConfigDefaultObjs = await getDefaultInfraConfigs(); + const defaultInfraConfigs = await getDefaultInfraConfigs(); - // Eliminate the rows (from 'infraConfigDefaultObjs') that are already present in the database table - const dbInfraConfigs = await this.prisma.infraConfig.findMany(); - const propsToInsert = infraConfigDefaultObjs.filter( - (p) => !dbInfraConfigs.find((e) => e.name === p.name), - ); + // Adding missing InfraConfigs to the database (with encrypted values) + const propsToInsert = + await getMissingInfraConfigEntries(defaultInfraConfigs); if (propsToInsert.length > 0) { await this.prisma.infraConfig.createMany({ data: propsToInsert }); + } + + // Encrypting previous InfraConfigs that are required to be encrypted + const encryptionRequiredEntries = + await getEncryptionRequiredInfraConfigEntries(defaultInfraConfigs); + + if (encryptionRequiredEntries.length > 0) { + const dbOperations = encryptionRequiredEntries.map((dbConfig) => { + return this.prisma.infraConfig.update({ + where: { name: dbConfig.name }, + data: { value: encrypt(dbConfig.value), isEncrypted: true }, + }); + }); + + await Promise.allSettled(dbOperations); + } + + // Sync the InfraConfigs with the .env file, if .env file updates later on + const envFileChangesRequired = await syncInfraConfigWithEnvFile(); + if (envFileChangesRequired.length > 0) { + const dbOperations = envFileChangesRequired.map((dbConfig) => { + const { id, ...dataObj } = dbConfig; + return this.prisma.infraConfig.update({ + where: { id: dbConfig.id }, + data: dataObj, + }); + }); + await Promise.allSettled(dbOperations); + } + + // Restart the app if needed + if ( + propsToInsert.length > 0 || + encryptionRequiredEntries.length > 0 || + envFileChangesRequired.length > 0 + ) { stopApp(); } } catch (error) { @@ -77,6 +123,7 @@ export class InfraConfigService implements OnModuleInit { // Prisma error code for 'Table does not exist' throwErr(DATABASE_TABLE_NOT_EXIST); } else { + console.log(error); throwErr(error); } } @@ -88,9 +135,24 @@ export class InfraConfigService implements OnModuleInit { * @returns InfraConfig model */ cast(dbInfraConfig: DBInfraConfig) { + switch (dbInfraConfig.name) { + case InfraConfigEnum.USER_HISTORY_STORE_ENABLED: + dbInfraConfig.value = + dbInfraConfig.value === 'true' + ? ServiceStatus.ENABLE + : ServiceStatus.DISABLE; + break; + default: + break; + } + + const plainValue = dbInfraConfig.isEncrypted + ? decrypt(dbInfraConfig.value) + : dbInfraConfig.value; + return { name: dbInfraConfig.name, - value: dbInfraConfig.value ?? '', + value: plainValue ?? '', }; } @@ -100,10 +162,16 @@ export class InfraConfigService implements OnModuleInit { */ async getInfraConfigsMap() { const infraConfigs = await this.prisma.infraConfig.findMany(); + const infraConfigMap: Record = {}; infraConfigs.forEach((config) => { - infraConfigMap[config.name] = config.value; + if (config.isEncrypted) { + infraConfigMap[config.name] = decrypt(config.value); + } else { + infraConfigMap[config.name] = config.value; + } }); + return infraConfigMap; } @@ -119,9 +187,14 @@ export class InfraConfigService implements OnModuleInit { if (E.isLeft(isValidate)) return E.left(isValidate.left); try { + const { isEncrypted } = await this.prisma.infraConfig.findUnique({ + where: { name }, + select: { isEncrypted: true }, + }); + const infraConfig = await this.prisma.infraConfig.update({ where: { name }, - data: { value }, + data: { value: isEncrypted ? encrypt(value) : value }, }); if (restartEnabled) stopApp(); @@ -147,11 +220,23 @@ export class InfraConfigService implements OnModuleInit { if (E.isLeft(isValidate)) return E.left(isValidate.left); try { + const dbInfraConfig = await this.prisma.infraConfig.findMany({ + select: { name: true, isEncrypted: true }, + }); + await this.prisma.$transaction(async (tx) => { for (let i = 0; i < infraConfigs.length; i++) { + const isEncrypted = dbInfraConfig.find( + (p) => p.name === infraConfigs[i].name, + )?.isEncrypted; + await tx.infraConfig.update({ where: { name: infraConfigs[i].name }, - data: { value: infraConfigs[i].value }, + data: { + value: isEncrypted + ? encrypt(infraConfigs[i].value) + : infraConfigs[i].value, + }, }); } }); @@ -209,7 +294,20 @@ export class InfraConfigService implements OnModuleInit { configMap.OIDC_USERINFO_URL ); case AuthProvider.EMAIL: - return configMap.MAILER_SMTP_URL && configMap.MAILER_ADDRESS_FROM; + if (configMap.MAILER_SMTP_ENABLE !== 'true') return false; + if (configMap.MAILER_USE_CUSTOM_CONFIGS === 'true') { + return ( + configMap.MAILER_SMTP_HOST && + configMap.MAILER_SMTP_PORT && + configMap.MAILER_SMTP_SECURE && + configMap.MAILER_SMTP_USER && + configMap.MAILER_SMTP_PASSWORD && + configMap.MAILER_TLS_REJECT_UNAUTHORIZED && + configMap.MAILER_ADDRESS_FROM + ); + } else { + return configMap.MAILER_SMTP_URL && configMap.MAILER_ADDRESS_FROM; + } default: return false; } @@ -231,6 +329,52 @@ export class InfraConfigService implements OnModuleInit { return E.right(isUpdated.right.value === 'true'); } + /** + * Enable or Disable SMTP + * @param status Status to enable or disable + * @returns Either true or an error + */ + async enableAndDisableSMTP(status: ServiceStatus) { + const isUpdated = await this.toggleServiceStatus( + InfraConfigEnum.MAILER_SMTP_ENABLE, + status, + true, + ); + if (E.isLeft(isUpdated)) return E.left(isUpdated.left); + + if (status === ServiceStatus.DISABLE) { + this.enableAndDisableSSO([{ provider: AuthProvider.EMAIL, status }]); + } + return E.right(true); + } + + /** + * Enable or Disable Service (i.e. ALLOW_AUDIT_LOGS, ALLOW_ANALYTICS_COLLECTION, ALLOW_DOMAIN_WHITELISTING, SITE_PROTECTION) + * @param configName Name of the InfraConfigEnum + * @param status Status to enable or disable + * @param restartEnabled If true, restart the app after updating the InfraConfig + * @returns Either true or an error + */ + async toggleServiceStatus( + configName: InfraConfigEnum, + status: ServiceStatus, + restartEnabled = false, + ) { + const isUpdated = await this.update( + configName, + status === ServiceStatus.ENABLE ? 'true' : 'false', + restartEnabled, + ); + if (E.isLeft(isUpdated)) return E.left(isUpdated.left); + + this.pubsub.publish( + `infra_config/${configName}/updated`, + isUpdated.right.value, + ); + + return E.right(true); + } + /** * Enable or Disable SSO for login/signup * @param provider Auth Provider to enable or disable @@ -238,10 +382,10 @@ export class InfraConfigService implements OnModuleInit { * @returns Either true or an error */ async enableAndDisableSSO(providerInfo: EnableAndDisableSSOArgs[]) { - const allowedAuthProviders = this.configService - .get('INFRA.VITE_ALLOWED_AUTH_PROVIDERS') - .split(','); + const infra = await this.get(InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS); + if (E.isLeft(infra)) return E.left(infra.left); + const allowedAuthProviders = infra.right.value.split(','); let updatedAuthProviders = allowedAuthProviders; const infraConfigMap = await this.getInfraConfigsMap(); @@ -296,6 +440,7 @@ export class InfraConfigService implements OnModuleInit { /** * Get InfraConfigs by names * @param names Names of the InfraConfigs + * @param checkDisallowedKeys If true, check if the names are allowed to fetch by client * @returns InfraConfig model */ async getMany(names: InfraConfigEnum[], checkDisallowedKeys: boolean = true) { @@ -328,6 +473,29 @@ export class InfraConfigService implements OnModuleInit { .split(','); } + /** + * Check if SMTP is enabled or not + * @returns boolean + */ + isSMTPEnabled() { + return ( + this.configService.get('INFRA.MAILER_SMTP_ENABLE') === 'true' + ); + } + + /** + * Check if user history is enabled or not + * @returns InfraConfig model + */ + async isUserHistoryEnabled() { + const infraConfig = await this.get( + InfraConfigEnum.USER_HISTORY_STORE_ENABLED, + ); + + if (E.isLeft(infraConfig)) return E.left(infraConfig.left); + return E.right(infraConfig.right); + } + /** * Reset all the InfraConfigs to their default values (from .env) */ @@ -375,6 +543,20 @@ export class InfraConfigService implements OnModuleInit { ) { for (let i = 0; i < infraConfigs.length; i++) { switch (infraConfigs[i].name) { + case InfraConfigEnum.MAILER_SMTP_ENABLE: + if ( + infraConfigs[i].value !== 'true' && + infraConfigs[i].value !== 'false' + ) + return E.left(INFRA_CONFIG_INVALID_INPUT); + break; + case InfraConfigEnum.MAILER_USE_CUSTOM_CONFIGS: + if ( + infraConfigs[i].value !== 'true' && + infraConfigs[i].value !== 'false' + ) + return E.left(INFRA_CONFIG_INVALID_INPUT); + break; case InfraConfigEnum.MAILER_SMTP_URL: const isValidUrl = validateSMTPUrl(infraConfigs[i].value); if (!isValidUrl) return E.left(INFRA_CONFIG_INVALID_INPUT); @@ -383,6 +565,32 @@ export class InfraConfigService implements OnModuleInit { const isValidEmail = validateSMTPEmail(infraConfigs[i].value); if (!isValidEmail) return E.left(INFRA_CONFIG_INVALID_INPUT); break; + case InfraConfigEnum.MAILER_SMTP_HOST: + if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT); + break; + case InfraConfigEnum.MAILER_SMTP_PORT: + if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT); + break; + case InfraConfigEnum.MAILER_SMTP_SECURE: + if ( + infraConfigs[i].value !== 'true' && + infraConfigs[i].value !== 'false' + ) + return E.left(INFRA_CONFIG_INVALID_INPUT); + break; + case InfraConfigEnum.MAILER_SMTP_USER: + if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT); + break; + case InfraConfigEnum.MAILER_SMTP_PASSWORD: + if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT); + break; + case InfraConfigEnum.MAILER_TLS_REJECT_UNAUTHORIZED: + if ( + infraConfigs[i].value !== 'true' && + infraConfigs[i].value !== 'false' + ) + return E.left(INFRA_CONFIG_INVALID_INPUT); + break; case InfraConfigEnum.GOOGLE_CLIENT_ID: if (!infraConfigs[i].value) return E.left(INFRA_CONFIG_INVALID_INPUT); break; diff --git a/packages/hoppscotch-backend/src/infra-token/infra-token.controller.ts b/packages/hoppscotch-backend/src/infra-token/infra-token.controller.ts new file mode 100644 index 0000000000..21e55ed139 --- /dev/null +++ b/packages/hoppscotch-backend/src/infra-token/infra-token.controller.ts @@ -0,0 +1,302 @@ +import { + Body, + Controller, + Delete, + Get, + HttpStatus, + Param, + Patch, + Post, + Query, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { plainToInstance } from 'class-transformer'; +import { AdminService } from 'src/admin/admin.service'; +import { InfraTokenGuard } from 'src/guards/infra-token.guard'; +import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard'; +import { + DeleteUserInvitationRequest, + DeleteUserInvitationResponse, + ExceptionResponse, + GetUserInvitationResponse, + GetUsersRequestQuery, + GetUserResponse, + UpdateUserRequest, + UpdateUserAdminStatusRequest, + UpdateUserAdminStatusResponse, + CreateUserInvitationRequest, + CreateUserInvitationResponse, + DeleteUserResponse, + GetUserWorkspacesResponse, +} from './request-response.dto'; +import * as E from 'fp-ts/Either'; +import * as O from 'fp-ts/Option'; +import { OffsetPaginationArgs } from 'src/types/input-types.args'; +import { + ApiBadRequestResponse, + ApiCreatedResponse, + ApiNotFoundResponse, + ApiOkResponse, + ApiSecurity, + ApiTags, +} from '@nestjs/swagger'; +import { throwHTTPErr } from 'src/utils'; +import { UserService } from 'src/user/user.service'; +import { + INFRA_TOKEN_CREATOR_NOT_FOUND, + USER_NOT_FOUND, + USERS_NOT_FOUND, +} from 'src/errors'; +import { InfraTokenService } from './infra-token.service'; +import { InfraTokenInterceptor } from 'src/interceptors/infra-token.interceptor'; +import { BearerToken } from 'src/decorators/bearer-token.decorator'; + +@ApiTags('User Management API') +@ApiSecurity('infra-token') +@UseGuards(ThrottlerBehindProxyGuard, InfraTokenGuard) +@UseInterceptors(InfraTokenInterceptor) +@Controller({ path: 'infra', version: '1' }) +export class InfraTokensController { + constructor( + private readonly infraTokenService: InfraTokenService, + private readonly adminService: AdminService, + private readonly userService: UserService, + ) {} + + @Post('user-invitations') + @ApiCreatedResponse({ + description: 'Create a user invitation', + type: CreateUserInvitationResponse, + }) + @ApiBadRequestResponse({ type: ExceptionResponse }) + @ApiNotFoundResponse({ type: ExceptionResponse }) + async createUserInvitation( + @BearerToken() token: string, + @Body() dto: CreateUserInvitationRequest, + ) { + const createdInvitations = + await this.infraTokenService.createUserInvitation(token, dto); + + if (E.isLeft(createdInvitations)) { + const statusCode = + (createdInvitations.left as string) === INFRA_TOKEN_CREATOR_NOT_FOUND + ? HttpStatus.NOT_FOUND + : HttpStatus.BAD_REQUEST; + + throwHTTPErr({ message: createdInvitations.left, statusCode }); + } + + return plainToInstance( + CreateUserInvitationResponse, + { invitationLink: process.env.VITE_BASE_URL }, + { + excludeExtraneousValues: true, + enableImplicitConversion: true, + }, + ); + } + + @Get('user-invitations') + @ApiOkResponse({ + description: 'Get pending user invitations', + type: [GetUserInvitationResponse], + }) + async getPendingUserInvitation( + @Query() paginationQuery: OffsetPaginationArgs, + ) { + const pendingInvitedUsers = + await this.adminService.fetchInvitedUsers(paginationQuery); + + return plainToInstance(GetUserInvitationResponse, pendingInvitedUsers, { + excludeExtraneousValues: true, + enableImplicitConversion: true, + }); + } + + @Delete('user-invitations') + @ApiOkResponse({ + description: 'Delete a pending user invitation', + type: DeleteUserInvitationResponse, + }) + @ApiBadRequestResponse({ type: ExceptionResponse }) + async deleteUserInvitation(@Body() dto: DeleteUserInvitationRequest) { + const isDeleted = await this.adminService.revokeUserInvitations( + dto.inviteeEmails, + ); + + if (E.isLeft(isDeleted)) { + throwHTTPErr({ + message: isDeleted.left, + statusCode: HttpStatus.BAD_REQUEST, + }); + } + + return plainToInstance( + DeleteUserInvitationResponse, + { message: isDeleted.right }, + { + excludeExtraneousValues: true, + enableImplicitConversion: true, + }, + ); + } + + @Get('users') + @ApiOkResponse({ + description: 'Get users list', + type: [GetUserResponse], + }) + async getUsers(@Query() query: GetUsersRequestQuery) { + const users = await this.userService.fetchAllUsersV2(query.searchString, { + take: query.take, + skip: query.skip, + }); + + return plainToInstance(GetUserResponse, users, { + excludeExtraneousValues: true, + enableImplicitConversion: true, + }); + } + + @Get('users/:uid') + @ApiOkResponse({ + description: 'Get user details', + type: GetUserResponse, + }) + @ApiNotFoundResponse({ type: ExceptionResponse }) + async getUser(@Param('uid') uid: string) { + const user = await this.userService.findUserById(uid); + + if (O.isNone(user)) { + throwHTTPErr({ + message: USER_NOT_FOUND, + statusCode: HttpStatus.NOT_FOUND, + }); + } + + return plainToInstance(GetUserResponse, user.value, { + excludeExtraneousValues: true, + enableImplicitConversion: true, + }); + } + + @Patch('users/:uid') + @ApiOkResponse({ + description: 'Update user display name', + type: GetUserResponse, + }) + @ApiBadRequestResponse({ type: ExceptionResponse }) + @ApiNotFoundResponse({ type: ExceptionResponse }) + async updateUser(@Param('uid') uid: string, @Body() body: UpdateUserRequest) { + const updatedUser = await this.userService.updateUserDisplayName( + uid, + body.displayName, + ); + + if (E.isLeft(updatedUser)) { + const statusCode = + (updatedUser.left as string) === USER_NOT_FOUND + ? HttpStatus.NOT_FOUND + : HttpStatus.BAD_REQUEST; + + throwHTTPErr({ message: updatedUser.left, statusCode }); + } + + return plainToInstance(GetUserResponse, updatedUser.right, { + excludeExtraneousValues: true, + enableImplicitConversion: true, + }); + } + + @Delete('users/:uid') + @ApiOkResponse({ + description: 'Delete a user from the instance', + type: DeleteUserResponse, + }) + @ApiBadRequestResponse({ type: ExceptionResponse }) + @ApiNotFoundResponse({ type: ExceptionResponse }) + async deleteUser(@Param('uid') uid: string) { + const deletedUser = await this.adminService.removeUserAccount(uid); + + if (E.isLeft(deletedUser)) { + const statusCode = + (deletedUser.left as string) === USER_NOT_FOUND + ? HttpStatus.NOT_FOUND + : HttpStatus.BAD_REQUEST; + + throwHTTPErr({ message: deletedUser.left, statusCode }); + } + + return plainToInstance( + DeleteUserResponse, + { message: deletedUser.right }, + { + excludeExtraneousValues: true, + enableImplicitConversion: true, + }, + ); + } + + @Patch('users/:uid/admin-status') + @ApiOkResponse({ + description: 'Update user admin status', + type: UpdateUserAdminStatusResponse, + }) + @ApiBadRequestResponse({ type: ExceptionResponse }) + @ApiNotFoundResponse({ type: ExceptionResponse }) + async updateUserAdminStatus( + @Param('uid') uid: string, + @Body() body: UpdateUserAdminStatusRequest, + ) { + let updatedUser; + + if (body.isAdmin) { + updatedUser = await this.adminService.makeUsersAdmin([uid]); + } else { + updatedUser = await this.adminService.demoteUsersByAdmin([uid]); + } + + if (E.isLeft(updatedUser)) { + const statusCode = + (updatedUser.left as string) === USERS_NOT_FOUND + ? HttpStatus.NOT_FOUND + : HttpStatus.BAD_REQUEST; + + throwHTTPErr({ message: updatedUser.left as string, statusCode }); + } + + return plainToInstance( + UpdateUserAdminStatusResponse, + { message: updatedUser.right }, + { + excludeExtraneousValues: true, + enableImplicitConversion: true, + }, + ); + } + + @Get('users/:uid/workspaces') + @ApiOkResponse({ + description: 'Get user workspaces', + type: [GetUserWorkspacesResponse], + }) + @ApiNotFoundResponse({ type: ExceptionResponse }) + async getUserWorkspaces(@Param('uid') uid: string) { + const userWorkspaces = await this.userService.fetchUserWorkspaces(uid); + + if (E.isLeft(userWorkspaces)) { + const statusCode = + userWorkspaces.left === USER_NOT_FOUND + ? HttpStatus.NOT_FOUND + : HttpStatus.BAD_REQUEST; + + throwHTTPErr({ message: userWorkspaces.left, statusCode }); + } + + return plainToInstance(GetUserWorkspacesResponse, userWorkspaces.right, { + excludeExtraneousValues: true, + enableImplicitConversion: true, + }); + } +} diff --git a/packages/hoppscotch-backend/src/infra-token/infra-token.model.ts b/packages/hoppscotch-backend/src/infra-token/infra-token.model.ts new file mode 100644 index 0000000000..9790830fe0 --- /dev/null +++ b/packages/hoppscotch-backend/src/infra-token/infra-token.model.ts @@ -0,0 +1,43 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; + +@ObjectType() +export class InfraToken { + @Field(() => ID, { + description: 'ID of the infra token', + }) + id: string; + + @Field(() => String, { + description: 'Label of the infra token', + }) + label: string; + + @Field(() => Date, { + description: 'Date when the infra token was created', + }) + createdOn: Date; + + @Field(() => Date, { + description: 'Date when the infra token expires', + nullable: true, + }) + expiresOn: Date; + + @Field(() => Date, { + description: 'Date when the infra token was last used', + }) + lastUsedOn: Date; +} + +@ObjectType() +export class CreateInfraTokenResponse { + @Field(() => String, { + description: 'The infra token', + }) + token: string; + + @Field(() => InfraToken, { + description: 'Infra token info', + }) + info: InfraToken; +} diff --git a/packages/hoppscotch-backend/src/infra-token/infra-token.module.ts b/packages/hoppscotch-backend/src/infra-token/infra-token.module.ts new file mode 100644 index 0000000000..a8e6f58c85 --- /dev/null +++ b/packages/hoppscotch-backend/src/infra-token/infra-token.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { PrismaModule } from 'src/prisma/prisma.module'; +import { InfraTokenResolver } from './infra-token.resolver'; +import { InfraTokenService } from './infra-token.service'; +import { InfraTokensController } from './infra-token.controller'; +import { AdminModule } from 'src/admin/admin.module'; +import { UserModule } from 'src/user/user.module'; + +@Module({ + imports: [PrismaModule, AdminModule, UserModule], + controllers: [InfraTokensController], + providers: [InfraTokenResolver, InfraTokenService], +}) +export class InfraTokenModule {} diff --git a/packages/hoppscotch-backend/src/infra-token/infra-token.resolver.ts b/packages/hoppscotch-backend/src/infra-token/infra-token.resolver.ts new file mode 100644 index 0000000000..c342732e08 --- /dev/null +++ b/packages/hoppscotch-backend/src/infra-token/infra-token.resolver.ts @@ -0,0 +1,68 @@ +import { Args, ID, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { CreateInfraTokenResponse, InfraToken } from './infra-token.model'; +import { UseGuards } from '@nestjs/common'; +import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard'; +import { InfraTokenService } from './infra-token.service'; +import { GqlAuthGuard } from 'src/guards/gql-auth.guard'; +import { GqlAdminGuard } from 'src/admin/guards/gql-admin.guard'; +import { OffsetPaginationArgs } from 'src/types/input-types.args'; +import { GqlAdmin } from 'src/admin/decorators/gql-admin.decorator'; +import { Admin } from 'src/admin/admin.model'; +import * as E from 'fp-ts/Either'; +import { throwErr } from 'src/utils'; + +@UseGuards(GqlThrottlerGuard) +@Resolver(() => InfraToken) +export class InfraTokenResolver { + constructor(private readonly infraTokenService: InfraTokenService) {} + + /* Query */ + + @Query(() => [InfraToken], { + description: 'Get list of infra tokens', + }) + @UseGuards(GqlAuthGuard, GqlAdminGuard) + infraTokens(@Args() args: OffsetPaginationArgs) { + return this.infraTokenService.getAll(args.take, args.skip); + } + + /* Mutations */ + + @Mutation(() => CreateInfraTokenResponse, { + description: 'Create a new infra token', + }) + @UseGuards(GqlAuthGuard, GqlAdminGuard) + async createInfraToken( + @GqlAdmin() admin: Admin, + @Args({ name: 'label', description: 'Label of the token' }) label: string, + @Args({ + name: 'expiryInDays', + description: 'Number of days the token is valid for', + nullable: true, + }) + expiryInDays: number, + ) { + const infraToken = await this.infraTokenService.create( + label, + expiryInDays, + admin, + ); + + if (E.isLeft(infraToken)) throwErr(infraToken.left); + return infraToken.right; + } + + @Mutation(() => Boolean, { + description: 'Revoke an infra token', + }) + @UseGuards(GqlAuthGuard, GqlAdminGuard) + async revokeInfraToken( + @Args({ name: 'id', type: () => ID, description: 'ID of the infra token' }) + id: string, + ) { + const res = await this.infraTokenService.revoke(id); + + if (E.isLeft(res)) throwErr(res.left); + return res.right; + } +} diff --git a/packages/hoppscotch-backend/src/infra-token/infra-token.service.ts b/packages/hoppscotch-backend/src/infra-token/infra-token.service.ts new file mode 100644 index 0000000000..887dfb3508 --- /dev/null +++ b/packages/hoppscotch-backend/src/infra-token/infra-token.service.ts @@ -0,0 +1,160 @@ +import { Injectable } from '@nestjs/common'; +import { InfraToken as dbInfraToken } from '@prisma/client'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { CreateInfraTokenResponse, InfraToken } from './infra-token.model'; +import { calculateExpirationDate, isValidLength } from 'src/utils'; +import { Admin } from 'src/admin/admin.model'; +import { + INFRA_TOKEN_CREATOR_NOT_FOUND, + INFRA_TOKEN_EXPIRY_INVALID, + INFRA_TOKEN_LABEL_SHORT, + INFRA_TOKEN_NOT_FOUND, +} from 'src/errors'; +import * as E from 'fp-ts/Either'; +import { CreateUserInvitationRequest } from './request-response.dto'; +import { AdminService } from 'src/admin/admin.service'; + +@Injectable() +export class InfraTokenService { + constructor( + private readonly prisma: PrismaService, + private readonly adminService: AdminService, + ) {} + + TITLE_LENGTH = 3; + VALID_TOKEN_DURATIONS = [7, 30, 60, 90]; + + /** + * Validate the expiration date of the token + * + * @param expiresOn Number of days the token is valid for + * @returns Boolean indicating if the expiration date is valid + */ + private validateExpirationDate(expiresOn: null | number) { + if (expiresOn === null || this.VALID_TOKEN_DURATIONS.includes(expiresOn)) + return true; + return false; + } + + /** + * Typecast a database InfraToken to a InfraToken model + * @param dbInfraToken database InfraToken + * @returns InfraToken model + */ + private cast(dbInfraToken: dbInfraToken): InfraToken { + return { + id: dbInfraToken.id, + label: dbInfraToken.label, + createdOn: dbInfraToken.createdOn, + expiresOn: dbInfraToken.expiresOn, + lastUsedOn: dbInfraToken.updatedOn, + }; + } + + /** + * Fetch all infra tokens with pagination + * @param take take for pagination + * @param skip skip for pagination + * @returns List of InfraToken models + */ + async getAll(take = 10, skip = 0) { + const infraTokens = await this.prisma.infraToken.findMany({ + take, + skip, + orderBy: { createdOn: 'desc' }, + }); + + return infraTokens.map((token) => this.cast(token)); + } + + /** + * Create a new infra token + * @param label label of the token + * @param expiryInDays expiry duration of the token + * @param admin admin who created the token + * @returns Either of error message or CreateInfraTokenResponse + */ + async create(label: string, expiryInDays: number, admin: Admin) { + if (!isValidLength(label, this.TITLE_LENGTH)) { + return E.left(INFRA_TOKEN_LABEL_SHORT); + } + + if (!this.validateExpirationDate(expiryInDays ?? null)) { + return E.left(INFRA_TOKEN_EXPIRY_INVALID); + } + + const createdInfraToken = await this.prisma.infraToken.create({ + data: { + creatorUid: admin.uid, + label, + expiresOn: calculateExpirationDate(expiryInDays ?? null) ?? undefined, + }, + }); + + const res: CreateInfraTokenResponse = { + token: createdInfraToken.token, + info: this.cast(createdInfraToken), + }; + + return E.right(res); + } + + /** + * Revoke an infra token + * @param id ID of the infra token + * @returns Either of error or true + */ + async revoke(id: string) { + try { + await this.prisma.infraToken.delete({ + where: { id }, + }); + } catch (error) { + return E.left(INFRA_TOKEN_NOT_FOUND); + } + return E.right(true); + } + + /** + * Update the last used on of an infra token + * @param token token to update + * @returns Either of error or InfraToken + */ + async updateLastUsedOn(token: string) { + try { + const infraToken = await this.prisma.infraToken.update({ + where: { token }, + data: { updatedOn: new Date() }, + }); + return E.right(this.cast(infraToken)); + } catch (error) { + return E.left(INFRA_TOKEN_NOT_FOUND); + } + } + + /** + * Create a user invitation using an infra token + * @param token token used to create the invitation + * @param dto CreateUserInvitationRequest + * @returns Either of error or InvitedUser + */ + async createUserInvitation(token: string, dto: CreateUserInvitationRequest) { + const infraToken = await this.prisma.infraToken.findUnique({ + where: { token }, + }); + + const tokenCreator = await this.prisma.user.findUnique({ + where: { uid: infraToken.creatorUid }, + }); + if (!tokenCreator) return E.left(INFRA_TOKEN_CREATOR_NOT_FOUND); + + const invitedUser = await this.adminService.inviteUserToSignInViaEmail( + tokenCreator.uid, + tokenCreator.email, + dto.inviteeEmail, + ); + if (E.isLeft(invitedUser)) return E.left(invitedUser.left); + + return E.right(invitedUser); + } +} diff --git a/packages/hoppscotch-backend/src/infra-token/request-response.dto.ts b/packages/hoppscotch-backend/src/infra-token/request-response.dto.ts new file mode 100644 index 0000000000..f3c64f8fc4 --- /dev/null +++ b/packages/hoppscotch-backend/src/infra-token/request-response.dto.ts @@ -0,0 +1,162 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Expose, Transform, Type } from 'class-transformer'; +import { + ArrayMinSize, + IsArray, + IsBoolean, + IsEmail, + IsNotEmpty, + IsOptional, + IsString, + MinLength, +} from 'class-validator'; +import { TeamMemberRole } from 'src/team/team.model'; +import { OffsetPaginationArgs } from 'src/types/input-types.args'; + +// POST v1/infra/user-invitations +export class CreateUserInvitationRequest { + @Type(() => String) + @IsNotEmpty() + @ApiProperty() + inviteeEmail: string; +} +export class CreateUserInvitationResponse { + @ApiProperty() + @Expose() + invitationLink: string; +} + +// GET v1/infra/user-invitations +export class GetUserInvitationResponse { + @ApiProperty() + @Expose() + inviteeEmail: string; + + @ApiProperty() + @Expose() + invitedOn: Date; +} + +// DELETE v1/infra/user-invitations +export class DeleteUserInvitationRequest { + @IsArray() + @ArrayMinSize(1) + @Type(() => String) + @IsNotEmpty() + @ApiProperty() + inviteeEmails: string[]; +} +export class DeleteUserInvitationResponse { + @ApiProperty() + @Expose() + message: string; +} + +// POST v1/infra/users +export class GetUsersRequestQuery extends OffsetPaginationArgs { + @IsOptional() + @IsString() + @MinLength(1) + @ApiPropertyOptional() + searchString: string; +} +export class GetUserResponse { + @ApiProperty() + @Expose() + uid: string; + + @ApiProperty() + @Expose() + displayName: string; + + @ApiProperty() + @Expose() + email: string; + + @ApiProperty() + @Expose() + photoURL: string; + + @ApiProperty() + @Expose() + isAdmin: boolean; + + @ApiProperty() + @Expose() + lastLoggedOn: Date; + + @ApiProperty() + @Expose() + lastActiveOn: Date; +} + +// PATCH v1/infra/users/:uid +export class UpdateUserRequest { + @IsOptional() + @IsString() + @MinLength(1) + @ApiPropertyOptional() + displayName: string; +} + +// PATCH v1/infra/users/:uid/admin-status +export class UpdateUserAdminStatusRequest { + @IsBoolean() + @IsNotEmpty() + @ApiProperty() + isAdmin: boolean; +} +export class UpdateUserAdminStatusResponse { + @ApiProperty() + @Expose() + message: string; +} + +// Used for Swagger doc only, in codebase throwHTTPErr function is used to throw errors +export class ExceptionResponse { + @ApiProperty() + @Expose() + message: string; + + @ApiProperty() + @Expose() + statusCode: number; +} + +// Delete v1/infra/users/:uid +export class DeleteUserResponse { + @ApiProperty() + @Expose() + message: string; +} + +// GET v1/infra/users/:uid/workspaces +export class GetUserWorkspacesResponse { + @ApiProperty() + @Expose() + id: string; + + @ApiProperty() + @Expose() + name: string; + + @ApiProperty({ enum: TeamMemberRole }) + @Expose() + role: string; + + @ApiProperty() + @Expose() + owner_count: number; + + @ApiProperty() + @Expose() + editor_count: number; + + @ApiProperty() + @Expose() + viewer_count: number; + + @ApiProperty() + @Expose() + member_count: number; +} diff --git a/packages/hoppscotch-backend/src/interceptors/access-token.interceptor.ts b/packages/hoppscotch-backend/src/interceptors/access-token.interceptor.ts new file mode 100644 index 0000000000..b9e5360948 --- /dev/null +++ b/packages/hoppscotch-backend/src/interceptors/access-token.interceptor.ts @@ -0,0 +1,36 @@ +import { + BadRequestException, + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { Observable, map } from 'rxjs'; +import { AccessTokenService } from 'src/access-token/access-token.service'; +import * as E from 'fp-ts/Either'; +import { ACCESS_TOKEN_NOT_FOUND } from 'src/errors'; + +@Injectable() +export class AccessTokenInterceptor implements NestInterceptor { + constructor(private readonly accessTokenService: AccessTokenService) {} + + intercept(context: ExecutionContext, handler: CallHandler): Observable { + const req = context.switchToHttp().getRequest(); + const authHeader = req.headers.authorization; + const token = authHeader && authHeader.split(' ')[1]; + if (!token) { + throw new BadRequestException(ACCESS_TOKEN_NOT_FOUND); + } + + return handler.handle().pipe( + map(async (data) => { + const userAccessToken = + await this.accessTokenService.updateLastUsedForPAT(token); + if (E.isLeft(userAccessToken)) + throw new BadRequestException(userAccessToken.left); + + return data; + }), + ); + } +} diff --git a/packages/hoppscotch-backend/src/interceptors/infra-token.interceptor.ts b/packages/hoppscotch-backend/src/interceptors/infra-token.interceptor.ts new file mode 100644 index 0000000000..bfddda748b --- /dev/null +++ b/packages/hoppscotch-backend/src/interceptors/infra-token.interceptor.ts @@ -0,0 +1,30 @@ +import { + BadRequestException, + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { INFRA_TOKEN_NOT_FOUND } from 'src/errors'; +import { InfraTokenService } from 'src/infra-token/infra-token.service'; + +@Injectable() +export class InfraTokenInterceptor implements NestInterceptor { + constructor(private readonly infraTokenService: InfraTokenService) {} + + intercept(context: ExecutionContext, handler: CallHandler): Observable { + const req = context.switchToHttp().getRequest(); + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + throw new BadRequestException(INFRA_TOKEN_NOT_FOUND); + } + + const token = authHeader.split(' ')[1]; + + this.infraTokenService.updateLastUsedOn(token); + + return handler.handle(); + } +} diff --git a/packages/hoppscotch-backend/src/interceptors/user-last-active-on.interceptor.ts b/packages/hoppscotch-backend/src/interceptors/user-last-active-on.interceptor.ts new file mode 100644 index 0000000000..faba057138 --- /dev/null +++ b/packages/hoppscotch-backend/src/interceptors/user-last-active-on.interceptor.ts @@ -0,0 +1,65 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, +} from '@nestjs/common'; +import { GqlContextType, GqlExecutionContext } from '@nestjs/graphql'; +import { Observable, throwError } from 'rxjs'; +import { catchError, tap } from 'rxjs/operators'; +import { AuthUser } from 'src/types/AuthUser'; +import { UserService } from 'src/user/user.service'; + +@Injectable() +export class UserLastActiveOnInterceptor implements NestInterceptor { + constructor(private userService: UserService) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + if (context.getType() === 'http') { + return this.restHandler(context, next); + } else if (context.getType() === 'graphql') { + return this.graphqlHandler(context, next); + } + } + + restHandler(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const user: AuthUser = request.user; + + return next.handle().pipe( + tap(() => { + if (user && typeof user === 'object') { + this.userService.updateUserLastActiveOn(user.uid); + } + }), + catchError((error) => { + if (user && typeof user === 'object') { + this.userService.updateUserLastActiveOn(user.uid); + } + return throwError(() => error); + }), + ); + } + + graphqlHandler( + context: ExecutionContext, + next: CallHandler, + ): Observable { + const contextObject = GqlExecutionContext.create(context).getContext(); + const user: AuthUser = contextObject?.req?.user; + + return next.handle().pipe( + tap(() => { + if (user && typeof user === 'object') { + this.userService.updateUserLastActiveOn(user.uid); + } + }), + catchError((error) => { + if (user && typeof user === 'object') { + this.userService.updateUserLastActiveOn(user.uid); + } + return throwError(() => error); + }), + ); + } +} diff --git a/packages/hoppscotch-backend/src/interceptors/user-last-login.interceptor.ts b/packages/hoppscotch-backend/src/interceptors/user-last-login.interceptor.ts new file mode 100644 index 0000000000..db019d9e66 --- /dev/null +++ b/packages/hoppscotch-backend/src/interceptors/user-last-login.interceptor.ts @@ -0,0 +1,25 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { AuthUser } from 'src/types/AuthUser'; +import { UserService } from 'src/user/user.service'; + +@Injectable() +export class UserLastLoginInterceptor implements NestInterceptor { + constructor(private userService: UserService) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const user: AuthUser = context.switchToHttp().getRequest().user; + + return next.handle().pipe( + tap(() => { + this.userService.updateUserLastLoggedOn(user.uid); + }), + ); + } +} diff --git a/packages/hoppscotch-backend/src/mailer/helper.ts b/packages/hoppscotch-backend/src/mailer/helper.ts new file mode 100644 index 0000000000..e5629418ec --- /dev/null +++ b/packages/hoppscotch-backend/src/mailer/helper.ts @@ -0,0 +1,59 @@ +import { TransportType } from '@nestjs-modules/mailer/dist/interfaces/mailer-options.interface'; +import { + MAILER_SMTP_PASSWORD_UNDEFINED, + MAILER_SMTP_URL_UNDEFINED, + MAILER_SMTP_USER_UNDEFINED, +} from 'src/errors'; +import { throwErr } from 'src/utils'; + +function isSMTPCustomConfigsEnabled(value) { + return value === 'true'; +} + +export function getMailerAddressFrom(env, config): string { + return ( + env.INFRA.MAILER_ADDRESS_FROM ?? + config.get('MAILER_ADDRESS_FROM') ?? + throwErr(MAILER_SMTP_URL_UNDEFINED) + ); +} + +export function getTransportOption(env, config): TransportType { + const useCustomConfigs = isSMTPCustomConfigsEnabled( + env.INFRA.MAILER_USE_CUSTOM_CONFIGS ?? + config.get('MAILER_USE_CUSTOM_CONFIGS'), + ); + + if (!useCustomConfigs) { + console.log('Using simple mailer configuration'); + return ( + env.INFRA.MAILER_SMTP_URL ?? + config.get('MAILER_SMTP_URL') ?? + throwErr(MAILER_SMTP_URL_UNDEFINED) + ); + } else { + console.log('Using advanced mailer configuration'); + return { + host: env.INFRA.MAILER_SMTP_HOST ?? config.get('MAILER_SMTP_HOST'), + port: +(env.INFRA.MAILER_SMTP_PORT ?? config.get('MAILER_SMTP_PORT')), + secure: + (env.INFRA.MAILER_SMTP_SECURE ?? config.get('MAILER_SMTP_SECURE')) === + 'true', + auth: { + user: + env.INFRA.MAILER_SMTP_USER ?? + config.get('MAILER_SMTP_USER') ?? + throwErr(MAILER_SMTP_USER_UNDEFINED), + pass: + env.INFRA.MAILER_SMTP_PASSWORD ?? + config.get('MAILER_SMTP_PASSWORD') ?? + throwErr(MAILER_SMTP_PASSWORD_UNDEFINED), + }, + tls: { + rejectUnauthorized: + (env.INFRA.MAILER_TLS_REJECT_UNAUTHORIZED ?? + config.get('MAILER_TLS_REJECT_UNAUTHORIZED')) === 'true', + }, + }; + } +} diff --git a/packages/hoppscotch-backend/src/mailer/mailer.module.ts b/packages/hoppscotch-backend/src/mailer/mailer.module.ts index a9d422d79d..f8d19726fa 100644 --- a/packages/hoppscotch-backend/src/mailer/mailer.module.ts +++ b/packages/hoppscotch-backend/src/mailer/mailer.module.ts @@ -2,13 +2,9 @@ import { Global, Module } from '@nestjs/common'; import { MailerModule as NestMailerModule } from '@nestjs-modules/mailer'; import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; import { MailerService } from './mailer.service'; -import { throwErr } from 'src/utils'; -import { - MAILER_FROM_ADDRESS_UNDEFINED, - MAILER_SMTP_URL_UNDEFINED, -} from 'src/errors'; import { ConfigService } from '@nestjs/config'; import { loadInfraConfiguration } from 'src/infra-config/helper'; +import { getMailerAddressFrom, getTransportOption } from './helper'; @Global() @Module({ @@ -18,24 +14,31 @@ import { loadInfraConfiguration } from 'src/infra-config/helper'; }) export class MailerModule { static async register() { + const config = new ConfigService(); const env = await loadInfraConfiguration(); - let mailerSmtpUrl = env.INFRA.MAILER_SMTP_URL; - let mailerAddressFrom = env.INFRA.MAILER_ADDRESS_FROM; - - if (!env.INFRA.MAILER_SMTP_URL || !env.INFRA.MAILER_ADDRESS_FROM) { - const config = new ConfigService(); - mailerSmtpUrl = config.get('MAILER_SMTP_URL'); - mailerAddressFrom = config.get('MAILER_ADDRESS_FROM'); + // If mailer SMTP is DISABLED, return the module without any configuration (service, listener, etc.) + if (env.INFRA.MAILER_SMTP_ENABLE !== 'true') { + console.log('Mailer module is disabled'); + return { + module: MailerModule, + }; } + // If mailer is ENABLED, return the module with configuration (service, etc.) + + // Determine transport configuration based on custom config flag + let transportOption = getTransportOption(env, config); + // Get mailer address from environment or config + const mailerAddressFrom = getMailerAddressFrom(env, config); + return { module: MailerModule, imports: [ NestMailerModule.forRoot({ - transport: mailerSmtpUrl ?? throwErr(MAILER_SMTP_URL_UNDEFINED), + transport: transportOption, defaults: { - from: mailerAddressFrom ?? throwErr(MAILER_FROM_ADDRESS_UNDEFINED), + from: mailerAddressFrom, }, template: { dir: __dirname + '/templates', diff --git a/packages/hoppscotch-backend/src/mailer/mailer.service.ts b/packages/hoppscotch-backend/src/mailer/mailer.service.ts index f71d65fe16..ab2abd1f6a 100644 --- a/packages/hoppscotch-backend/src/mailer/mailer.service.ts +++ b/packages/hoppscotch-backend/src/mailer/mailer.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Optional } from '@nestjs/common'; import { AdminUserInvitationMailDescription, MailDescription, @@ -7,10 +7,14 @@ import { import { throwErr } from 'src/utils'; import { EMAIL_FAILED } from 'src/errors'; import { MailerService as NestMailerService } from '@nestjs-modules/mailer'; +import { ConfigService } from '@nestjs/config'; @Injectable() export class MailerService { - constructor(private readonly nestMailerService: NestMailerService) {} + constructor( + @Optional() private readonly nestMailerService: NestMailerService, + private readonly configService: ConfigService, + ) {} /** * Takes an input mail description and spits out the Email subject required for it @@ -42,6 +46,8 @@ export class MailerService { to: string, mailDesc: MailDescription | UserMagicLinkMailDescription, ) { + if (this.configService.get('INFRA.MAILER_SMTP_ENABLE') !== 'true') return; + try { await this.nestMailerService.sendMail({ to, @@ -50,6 +56,7 @@ export class MailerService { context: mailDesc.variables, }); } catch (error) { + console.log('Error from sendEmail:', error); return throwErr(EMAIL_FAILED); } } @@ -64,6 +71,8 @@ export class MailerService { to: string, mailDesc: AdminUserInvitationMailDescription, ) { + if (this.configService.get('INFRA.MAILER_SMTP_ENABLE') !== 'true') return; + try { const res = await this.nestMailerService.sendMail({ to, @@ -73,6 +82,7 @@ export class MailerService { }); return res; } catch (error) { + console.log('Error from sendUserInvitationEmail:', error); return throwErr(EMAIL_FAILED); } } diff --git a/packages/hoppscotch-backend/src/main.ts b/packages/hoppscotch-backend/src/main.ts index a32079c74f..c8eed7724e 100644 --- a/packages/hoppscotch-backend/src/main.ts +++ b/packages/hoppscotch-backend/src/main.ts @@ -2,11 +2,40 @@ import { NestFactory } from '@nestjs/core'; import { json } from 'express'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; -import { VersioningType } from '@nestjs/common'; +import { ValidationPipe, VersioningType } from '@nestjs/common'; import * as session from 'express-session'; import { emitGQLSchemaFile } from './gql-schema'; import { checkEnvironmentAuthProvider } from './utils'; import { ConfigService } from '@nestjs/config'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { InfraTokensController } from './infra-token/infra-token.controller'; +import { InfraTokenModule } from './infra-token/infra-token.module'; + +function setupSwagger(app) { + const swaggerDocPath = '/api-docs'; + + const config = new DocumentBuilder() + .setTitle('Hoppscotch API Documentation') + .setDescription('APIs for external integration') + .addApiKey( + { + type: 'apiKey', + name: 'Authorization', + in: 'header', + scheme: 'bearer', + bearerFormat: 'Bearer', + }, + 'infra-token', + ) + .build(); + + const document = SwaggerModule.createDocument(app, config, { + include: [InfraTokenModule], + }); + SwaggerModule.setup(swaggerDocPath, app, document, { + swaggerOptions: { persistAuthorization: true, ignoreGlobalPrefix: true }, + }); +} async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -53,6 +82,14 @@ async function bootstrap() { type: VersioningType.URI, }); app.use(cookieParser()); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + }), + ); + + await setupSwagger(app); + await app.listen(configService.get('PORT') || 3170); // Graceful shutdown diff --git a/packages/hoppscotch-backend/src/plugins/GQLComplexityPlugin.ts b/packages/hoppscotch-backend/src/plugins/GQLComplexityPlugin.ts index 52b894557a..31655facfc 100644 --- a/packages/hoppscotch-backend/src/plugins/GQLComplexityPlugin.ts +++ b/packages/hoppscotch-backend/src/plugins/GQLComplexityPlugin.ts @@ -7,6 +7,7 @@ import { import { Plugin } from '@nestjs/apollo'; import { GraphQLError } from 'graphql'; import { + ComplexityEstimatorArgs, fieldExtensionsEstimator, getComplexity, simpleEstimator, @@ -29,6 +30,14 @@ export class GQLComplexityPlugin implements ApolloServerPlugin { query: document, variables: request.variables, estimators: [ + // Custom estimator for introspection fields + (args: ComplexityEstimatorArgs) => { + const fieldName = args.field.name; + if (fieldName.startsWith('__')) { + return 0; // Return 0 complexity for introspection fields + } + return; + }, fieldExtensionsEstimator(), simpleEstimator({ defaultComplexity: 1 }), ], diff --git a/packages/hoppscotch-backend/src/pubsub/topicsDefs.ts b/packages/hoppscotch-backend/src/pubsub/topicsDefs.ts index 49506c67e9..d9b7b246e6 100644 --- a/packages/hoppscotch-backend/src/pubsub/topicsDefs.ts +++ b/packages/hoppscotch-backend/src/pubsub/topicsDefs.ts @@ -23,6 +23,7 @@ import { TeamInvitation } from 'src/team-invitation/team-invitation.model'; import { InvitedUser } from '../admin/invited-user.model'; import { UserCollection, + UserCollectionDuplicatedData, UserCollectionRemovedData, UserCollectionReorderData, } from 'src/user-collection/user-collections.model'; @@ -42,12 +43,14 @@ export type TopicDef = { topic: `user_request/${string}/${'created' | 'updated' | 'deleted'}` ]: UserRequest; [topic: `user_request/${string}/${'moved'}`]: UserRequestReorderData; - [ - topic: `user_history/${string}/${'created' | 'updated' | 'deleted'}` - ]: UserHistory; + [topic: `user_history/${string}/${'created' | 'updated' | 'deleted'}`]: + | UserHistory + | boolean; + [topic: `user_history/${string}/deleted_many`]: UserHistoryDeletedManyData; [ topic: `user_coll/${string}/${'created' | 'updated' | 'moved'}` ]: UserCollection; + [topic: `user_coll/${string}/${'duplicated'}`]: UserCollectionDuplicatedData; [topic: `user_coll/${string}/${'deleted'}`]: UserCollectionRemovedData; [topic: `user_coll/${string}/${'order_updated'}`]: UserCollectionReorderData; [topic: `team/${string}/member_removed`]: string; @@ -61,7 +64,6 @@ export type TopicDef = { [topic: `team_coll/${string}/${'coll_removed'}`]: string; [topic: `team_coll/${string}/${'coll_moved'}`]: TeamCollection; [topic: `team_coll/${string}/${'coll_order_updated'}`]: CollectionReorderData; - [topic: `user_history/${string}/deleted_many`]: UserHistoryDeletedManyData; [ topic: `team_req/${string}/${'req_created' | 'req_updated' | 'req_moved'}` ]: TeamRequest; @@ -72,4 +74,5 @@ export type TopicDef = { [ topic: `shortcode/${string}/${'created' | 'revoked' | 'updated'}` ]: Shortcode; + [topic: `infra_config/${string}/${'updated'}`]: string; }; diff --git a/packages/hoppscotch-backend/src/shortcode/shortcode.service.spec.ts b/packages/hoppscotch-backend/src/shortcode/shortcode.service.spec.ts index 7614fd0a49..94b2c8c7bf 100644 --- a/packages/hoppscotch-backend/src/shortcode/shortcode.service.spec.ts +++ b/packages/hoppscotch-backend/src/shortcode/shortcode.service.spec.ts @@ -48,6 +48,8 @@ const user: AuthUser = { photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute', isAdmin: false, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: createdOn, + lastActiveOn: createdOn, createdOn: createdOn, currentGQLSession: {}, currentRESTSession: {}, @@ -544,10 +546,12 @@ describe('ShortcodeService', () => { ); expect(result).toEqual([ { - id: shortcodes[1].id, - request: JSON.stringify(shortcodes[1].request), - properties: JSON.stringify(shortcodes[1].embedProperties), - createdOn: shortcodes[1].createdOn, + id: shortcodesWithUserEmail[1].id, + request: JSON.stringify(shortcodesWithUserEmail[1].request), + properties: JSON.stringify( + shortcodesWithUserEmail[1].embedProperties, + ), + createdOn: shortcodesWithUserEmail[1].createdOn, creator: { uid: user.uid, email: user.email, diff --git a/packages/hoppscotch-backend/src/shortcode/shortcode.service.ts b/packages/hoppscotch-backend/src/shortcode/shortcode.service.ts index e6a02fdb19..e864230535 100644 --- a/packages/hoppscotch-backend/src/shortcode/shortcode.service.ts +++ b/packages/hoppscotch-backend/src/shortcode/shortcode.service.ts @@ -299,7 +299,10 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit { where: userEmail ? { User: { - email: userEmail, + email: { + equals: userEmail, + mode: 'insensitive', + }, }, } : undefined, diff --git a/packages/hoppscotch-backend/src/team-collection/helper.ts b/packages/hoppscotch-backend/src/team-collection/helper.ts index 1958a0a504..b88577126b 100644 --- a/packages/hoppscotch-backend/src/team-collection/helper.ts +++ b/packages/hoppscotch-backend/src/team-collection/helper.ts @@ -1,3 +1,5 @@ +import { TeamRequest } from '@prisma/client'; + // Type of data returned from the query to obtain all search results export type SearchQueryReturnType = { id: string; @@ -12,3 +14,12 @@ export type ParentTreeQueryReturnType = { parentID: string; title: string; }; +// Type of data returned from the query to fetch collection details from CLI +export type GetCollectionResponse = { + id: string; + data: string | null; + title: string; + parentID: string | null; + folders: GetCollectionResponse[]; + requests: TeamRequest[]; +}; diff --git a/packages/hoppscotch-backend/src/team-collection/input-type.args.ts b/packages/hoppscotch-backend/src/team-collection/input-type.args.ts index 2b4dd20a80..6e999f18e2 100644 --- a/packages/hoppscotch-backend/src/team-collection/input-type.args.ts +++ b/packages/hoppscotch-backend/src/team-collection/input-type.args.ts @@ -47,14 +47,12 @@ export class RenameTeamCollectionArgs { @Field(() => ID, { name: 'collectionID', description: 'ID of the collection', - deprecationReason: 'Switch to updateTeamCollection mutation instead', }) collectionID: string; @Field({ name: 'newTitle', description: 'The updated title of the collection', - deprecationReason: 'Switch to updateTeamCollection mutation instead', }) newTitle: string; } diff --git a/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts b/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts index f5a2ccfb8c..1fc4c10863 100644 --- a/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts +++ b/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts @@ -102,6 +102,36 @@ export class TeamCollectionResolver { return jsonString.right; } + @Query(() => String, { + description: + 'Returns a JSON string of all the contents of a Team Collection', + }) + @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) + @RequiresTeamRole( + TeamMemberRole.VIEWER, + TeamMemberRole.EDITOR, + TeamMemberRole.OWNER, + ) + async exportCollectionToJSON( + @Args({ name: 'teamID', description: 'ID of the team', type: () => ID }) + teamID: string, + @Args({ + name: 'collectionID', + description: 'ID of the collection', + type: () => ID, + }) + collectionID: string, + ) { + const collectionData = + await this.teamCollectionService.exportCollectionToJSONObject( + teamID, + collectionID, + ); + + if (E.isLeft(collectionData)) throwErr(collectionData.left as string); + return JSON.stringify(collectionData.right); + } + @Query(() => [TeamCollection], { description: 'Returns the collections of a team', }) @@ -331,6 +361,26 @@ export class TeamCollectionResolver { return updatedTeamCollection.right; } + @Mutation(() => Boolean, { + description: 'Duplicate a Team Collection', + }) + @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) + @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + async duplicateTeamCollection( + @Args({ + name: 'collectionID', + description: 'ID of the collection', + }) + collectionID: string, + ) { + const duplicatedTeamCollection = + await this.teamCollectionService.duplicateTeamCollection(collectionID); + + if (E.isLeft(duplicatedTeamCollection)) + throwErr(duplicatedTeamCollection.left); + return duplicatedTeamCollection.right; + } + // Subscriptions @Subscription(() => TeamCollection, { diff --git a/packages/hoppscotch-backend/src/team-collection/team-collection.service.spec.ts b/packages/hoppscotch-backend/src/team-collection/team-collection.service.spec.ts index 041e3858ce..b21cc76402 100644 --- a/packages/hoppscotch-backend/src/team-collection/team-collection.service.spec.ts +++ b/packages/hoppscotch-backend/src/team-collection/team-collection.service.spec.ts @@ -12,6 +12,7 @@ import { TEAM_COL_REORDERING_FAILED, TEAM_COL_SAME_NEXT_COLL, TEAM_INVALID_COLL_ID, + TEAM_MEMBER_NOT_FOUND, TEAM_NOT_OWNER, } from 'src/errors'; import { PrismaService } from 'src/prisma/prisma.service'; @@ -19,15 +20,18 @@ import { PubSubService } from 'src/pubsub/pubsub.service'; import { AuthUser } from 'src/types/AuthUser'; import { TeamCollectionService } from './team-collection.service'; import { TeamCollection } from './team-collection.model'; +import { TeamService } from 'src/team/team.service'; const mockPrisma = mockDeep(); const mockPubSub = mockDeep(); +const mockTeamService = mockDeep(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const teamCollectionService = new TeamCollectionService( mockPrisma, mockPubSub as any, + mockTeamService, ); const currentTime = new Date(); @@ -39,6 +43,8 @@ const user: AuthUser = { photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute', isAdmin: false, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, currentGQLSession: {}, currentRESTSession: {}, @@ -1738,3 +1744,63 @@ describe('updateTeamCollection', () => { }); //ToDo: write test cases for exportCollectionsToJSON + +describe('getCollectionForCLI', () => { + test('should throw TEAM_COLL_NOT_FOUND if collectionID is invalid', async () => { + mockPrisma.teamCollection.findUniqueOrThrow.mockRejectedValueOnce( + 'NotFoundError', + ); + + const result = await teamCollectionService.getCollectionForCLI( + 'invalidID', + user.uid, + ); + expect(result).toEqualLeft(TEAM_COLL_NOT_FOUND); + }); + + test('should throw TEAM_MEMBER_NOT_FOUND if user not in same team', async () => { + mockPrisma.teamCollection.findUniqueOrThrow.mockResolvedValueOnce( + rootTeamCollection, + ); + mockTeamService.getTeamMember.mockResolvedValue(null); + + const result = await teamCollectionService.getCollectionForCLI( + rootTeamCollection.id, + user.uid, + ); + expect(result).toEqualLeft(TEAM_MEMBER_NOT_FOUND); + }); + + // test('should return the TeamCollection data for CLI', async () => { + // mockPrisma.teamCollection.findUniqueOrThrow.mockResolvedValueOnce( + // rootTeamCollection, + // ); + // mockTeamService.getTeamMember.mockResolvedValue({ + // membershipID: 'sdc3sfdv', + // userUid: user.uid, + // role: TeamMemberRole.OWNER, + // }); + + // const result = await teamCollectionService.getCollectionForCLI( + // rootTeamCollection.id, + // user.uid, + // ); + // expect(result).toEqualRight({ + // id: rootTeamCollection.id, + // data: JSON.stringify(rootTeamCollection.data), + // title: rootTeamCollection.title, + // parentID: rootTeamCollection.parentID, + // folders: [ + // { + // id: childTeamCollection.id, + // data: JSON.stringify(childTeamCollection.data), + // title: childTeamCollection.title, + // parentID: childTeamCollection.parentID, + // folders: [], + // requests: [], + // }, + // ], + // requests: [], + // }); + // }); +}); diff --git a/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts b/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts index 76dd454115..6779e64aab 100644 --- a/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts +++ b/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts @@ -18,23 +18,38 @@ import { TEAM_COL_SEARCH_FAILED, TEAM_REQ_PARENT_TREE_GEN_FAILED, TEAM_COLL_PARENT_TREE_GEN_FAILED, + TEAM_MEMBER_NOT_FOUND, } from '../errors'; import { PubSubService } from '../pubsub/pubsub.service'; -import { escapeSqlLikeString, isValidLength } from 'src/utils'; +import { + escapeSqlLikeString, + isValidLength, + transformCollectionData, +} from 'src/utils'; import * as E from 'fp-ts/Either'; import * as O from 'fp-ts/Option'; -import { Prisma, TeamCollection as DBTeamCollection } from '@prisma/client'; +import { + Prisma, + TeamCollection as DBTeamCollection, + TeamRequest, +} from '@prisma/client'; import { CollectionFolder } from 'src/types/CollectionFolder'; import { stringToJson } from 'src/utils'; import { CollectionSearchNode } from 'src/types/CollectionSearchNode'; -import { ParentTreeQueryReturnType, SearchQueryReturnType } from './helper'; +import { + GetCollectionResponse, + ParentTreeQueryReturnType, + SearchQueryReturnType, +} from './helper'; import { RESTError } from 'src/types/RESTError'; +import { TeamService } from 'src/team/team.service'; @Injectable() export class TeamCollectionService { constructor( private readonly prisma: PrismaService, private readonly pubsub: PubSubService, + private readonly teamService: TeamService, ) {} TITLE_LENGTH = 3; @@ -88,10 +103,10 @@ export class TeamCollectionService { * @param collectionID The Collection ID * @returns A JSON string containing all the contents of a collection */ - private async exportCollectionToJSONObject( + async exportCollectionToJSONObject( teamID: string, collectionID: string, - ) { + ): Promise | E.Left> { const collection = await this.getCollection(collectionID); if (E.isLeft(collection)) return E.left(TEAM_INVALID_COLL_ID); @@ -123,11 +138,13 @@ export class TeamCollectionService { }, }); + const data = transformCollectionData(collection.right.data); + const result: CollectionFolder = { name: collection.right.title, folders: childrenCollectionObjects, requests: requests.map((x) => x.request), - data: JSON.stringify(collection.right.data), + data, }; return E.right(result); @@ -298,11 +315,13 @@ export class TeamCollectionService { * @returns TeamCollection model */ private cast(teamCollection: DBTeamCollection): TeamCollection { + const data = transformCollectionData(teamCollection.data); + return { id: teamCollection.id, title: teamCollection.title, parentID: teamCollection.parentID, - data: !teamCollection.data ? null : JSON.stringify(teamCollection.data), + data, }; } @@ -1344,4 +1363,126 @@ export class TeamCollectionService { return E.left(TEAM_REQ_PARENT_TREE_GEN_FAILED); } } + + /** + * Get all requests in a collection + * + * @param collectionID The Collection ID + * @returns A list of all requests in the collection + */ + private async getAllRequestsInCollection(collectionID: string) { + const dbTeamRequests = await this.prisma.teamRequest.findMany({ + where: { + collectionID: collectionID, + }, + orderBy: { + orderIndex: 'asc', + }, + }); + + const teamRequests = dbTeamRequests.map((tr) => { + return { + id: tr.id, + collectionID: tr.collectionID, + teamID: tr.teamID, + title: tr.title, + request: JSON.stringify(tr.request), + }; + }); + + return teamRequests; + } + + /** + * Get Collection Tree for CLI + * + * @param parentID The parent Collection ID + * @returns Collection tree for CLI + */ + private async getCollectionTreeForCLI(parentID: string | null) { + const childCollections = await this.prisma.teamCollection.findMany({ + where: { parentID }, + orderBy: { orderIndex: 'asc' }, + }); + + const response: GetCollectionResponse[] = []; + + for (const collection of childCollections) { + const folder: GetCollectionResponse = { + id: collection.id, + data: collection.data === null ? null : JSON.stringify(collection.data), + title: collection.title, + parentID: collection.parentID, + folders: await this.getCollectionTreeForCLI(collection.id), + requests: await this.getAllRequestsInCollection(collection.id), + }; + + response.push(folder); + } + + return response; + } + + /** + * Get Collection for CLI + * + * @param collectionID The Collection ID + * @param userUid The User UID + * @returns An Either of the Collection details + */ + async getCollectionForCLI(collectionID: string, userUid: string) { + try { + const collection = await this.prisma.teamCollection.findUniqueOrThrow({ + where: { id: collectionID }, + }); + + const teamMember = await this.teamService.getTeamMember( + collection.teamID, + userUid, + ); + if (!teamMember) return E.left(TEAM_MEMBER_NOT_FOUND); + + return E.right({ + id: collection.id, + data: collection.data === null ? null : JSON.stringify(collection.data), + title: collection.title, + parentID: collection.parentID, + folders: await this.getCollectionTreeForCLI(collection.id), + requests: await this.getAllRequestsInCollection(collection.id), + }); + } catch (error) { + return E.left(TEAM_COLL_NOT_FOUND); + } + } + + /** + * Duplicate a Team Collection + * + * @param collectionID The Collection ID + * @returns Boolean of duplication status + */ + async duplicateTeamCollection(collectionID: string) { + const collection = await this.getCollection(collectionID); + if (E.isLeft(collection)) return E.left(TEAM_INVALID_COLL_ID); + + const collectionJSONObject = await this.exportCollectionToJSONObject( + collection.right.teamID, + collectionID, + ); + if (E.isLeft(collectionJSONObject)) return E.left(TEAM_INVALID_COLL_ID); + + const result = await this.importCollectionsFromJSON( + JSON.stringify([ + { + ...collectionJSONObject.right, + name: `${collection.right.title} - Duplicate`, + }, + ]), + collection.right.teamID, + collection.right.parentID, + ); + if (E.isLeft(result)) return E.left(result.left as string); + + return E.right(true); + } } diff --git a/packages/hoppscotch-backend/src/team-environments/team-environments.service.spec.ts b/packages/hoppscotch-backend/src/team-environments/team-environments.service.spec.ts index 466dddf697..a1f4d0a232 100644 --- a/packages/hoppscotch-backend/src/team-environments/team-environments.service.spec.ts +++ b/packages/hoppscotch-backend/src/team-environments/team-environments.service.spec.ts @@ -6,19 +6,24 @@ import { JSON_INVALID, TEAM_ENVIRONMENT_NOT_FOUND, TEAM_ENVIRONMENT_SHORT_NAME, + TEAM_MEMBER_NOT_FOUND, } from 'src/errors'; +import { TeamService } from 'src/team/team.service'; +import { TeamMemberRole } from 'src/team/team.model'; const mockPrisma = mockDeep(); const mockPubSub = { publish: jest.fn().mockResolvedValue(null), }; +const mockTeamService = mockDeep(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const teamEnvironmentsService = new TeamEnvironmentsService( mockPrisma, mockPubSub as any, + mockTeamService, ); const teamEnvironment = { @@ -380,4 +385,47 @@ describe('TeamEnvironmentsService', () => { expect(result).toEqual(0); }); }); + + describe('getTeamEnvironmentForCLI', () => { + test('should successfully return a TeamEnvironment with valid ID', async () => { + mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce( + teamEnvironment, + ); + mockTeamService.getTeamMember.mockResolvedValue({ + membershipID: 'sdc3sfdv', + userUid: '123454', + role: TeamMemberRole.OWNER, + }); + + const result = await teamEnvironmentsService.getTeamEnvironmentForCLI( + teamEnvironment.id, + '123454', + ); + expect(result).toEqualRight(teamEnvironment); + }); + + test('should throw TEAM_ENVIRONMENT_NOT_FOUND with invalid ID', async () => { + mockPrisma.teamEnvironment.findFirstOrThrow.mockRejectedValueOnce( + 'RejectOnNotFound', + ); + + const result = await teamEnvironmentsService.getTeamEnvironment( + teamEnvironment.id, + ); + expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND); + }); + + test('should throw TEAM_MEMBER_NOT_FOUND if user not in same team', async () => { + mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce( + teamEnvironment, + ); + mockTeamService.getTeamMember.mockResolvedValue(null); + + const result = await teamEnvironmentsService.getTeamEnvironmentForCLI( + teamEnvironment.id, + '333', + ); + expect(result).toEqualLeft(TEAM_MEMBER_NOT_FOUND); + }); + }); }); diff --git a/packages/hoppscotch-backend/src/team-environments/team-environments.service.ts b/packages/hoppscotch-backend/src/team-environments/team-environments.service.ts index 7af9481e50..91779e4b5e 100644 --- a/packages/hoppscotch-backend/src/team-environments/team-environments.service.ts +++ b/packages/hoppscotch-backend/src/team-environments/team-environments.service.ts @@ -6,14 +6,17 @@ import { TeamEnvironment } from './team-environments.model'; import { TEAM_ENVIRONMENT_NOT_FOUND, TEAM_ENVIRONMENT_SHORT_NAME, + TEAM_MEMBER_NOT_FOUND, } from 'src/errors'; import * as E from 'fp-ts/Either'; import { isValidLength } from 'src/utils'; +import { TeamService } from 'src/team/team.service'; @Injectable() export class TeamEnvironmentsService { constructor( private readonly prisma: PrismaService, private readonly pubsub: PubSubService, + private readonly teamService: TeamService, ) {} TITLE_LENGTH = 3; @@ -191,7 +194,7 @@ export class TeamEnvironmentsService { const result = await this.prisma.teamEnvironment.create({ data: { - name: environment.name, + name: `${environment.name} - Duplicate`, teamID: environment.teamID, variables: environment.variables as Prisma.JsonArray, }, @@ -242,4 +245,30 @@ export class TeamEnvironmentsService { }); return envCount; } + + /** + * Get details of a TeamEnvironment for CLI. + * + * @param id TeamEnvironment ID + * @param userUid User UID + * @returns Either of a TeamEnvironment or error message + */ + async getTeamEnvironmentForCLI(id: string, userUid: string) { + try { + const teamEnvironment = + await this.prisma.teamEnvironment.findFirstOrThrow({ + where: { id }, + }); + + const teamMember = await this.teamService.getTeamMember( + teamEnvironment.teamID, + userUid, + ); + if (!teamMember) return E.left(TEAM_MEMBER_NOT_FOUND); + + return E.right(teamEnvironment); + } catch (error) { + return E.left(TEAM_ENVIRONMENT_NOT_FOUND); + } + } } diff --git a/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts b/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts index f7749070fe..db29274994 100644 --- a/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts +++ b/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts @@ -75,12 +75,13 @@ export class TeamInvitationService { if (!isEmailValid) return E.left(INVALID_EMAIL); try { - const teamInvite = await this.prisma.teamInvitation.findUniqueOrThrow({ + const teamInvite = await this.prisma.teamInvitation.findFirstOrThrow({ where: { - teamID_inviteeEmail: { - inviteeEmail: inviteeEmail, - teamID: teamID, + inviteeEmail: { + equals: inviteeEmail, + mode: 'insensitive', }, + teamID, }, }); diff --git a/packages/hoppscotch-backend/src/team/team.service.ts b/packages/hoppscotch-backend/src/team/team.service.ts index f1afcf9083..80a2240b13 100644 --- a/packages/hoppscotch-backend/src/team/team.service.ts +++ b/packages/hoppscotch-backend/src/team/team.service.ts @@ -38,7 +38,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { canAllowUserDeletion(user: AuthUser): TO.TaskOption { return pipe( - this.isUserOwnerRoleInTeams(user.uid), + this.isUserSoleOwnerInAnyTeam(user.uid), TO.fromTask, TO.chain((isOwner) => (isOwner ? TO.some(USER_IS_OWNER) : TO.none)), ); @@ -396,18 +396,34 @@ export class TeamService implements UserDataHandler, OnModuleInit { return teamMember ? teamMember.role : null; } - isUserOwnerRoleInTeams(uid: string): T.Task { - return pipe( - () => - this.prisma.teamMember.count({ + isUserSoleOwnerInAnyTeam(uid: string): T.Task { + return async () => { + // Find all teams where the user is an OWNER + const userOwnedTeams = await this.prisma.teamMember.findMany({ + where: { + userUid: uid, + role: TeamMemberRole.OWNER, + }, + select: { + teamID: true, + }, + }); + + for (const userOwnedTeam of userOwnedTeams) { + const ownerCount = await this.prisma.teamMember.count({ where: { - userUid: uid, + teamID: userOwnedTeam.teamID, role: TeamMemberRole.OWNER, }, - take: 1, - }), - T.map((count) => count > 0), - ); + }); + + // early return true if the user is the sole owner + if (ownerCount === 1) return true; + } + + // return false if the user is not the sole owner in any team + return false; + }; } deleteUserFromAllTeams(uid: string) { diff --git a/packages/hoppscotch-backend/src/types/AccessToken.ts b/packages/hoppscotch-backend/src/types/AccessToken.ts new file mode 100644 index 0000000000..b467eafd37 --- /dev/null +++ b/packages/hoppscotch-backend/src/types/AccessToken.ts @@ -0,0 +1,7 @@ +export type AccessToken = { + id: string; + label: string; + createdOn: Date; + lastUsedOn: Date; + expiresOn: null | Date; +}; diff --git a/packages/hoppscotch-backend/src/types/InfraConfig.ts b/packages/hoppscotch-backend/src/types/InfraConfig.ts index 7798e23eac..9ff49c90ee 100644 --- a/packages/hoppscotch-backend/src/types/InfraConfig.ts +++ b/packages/hoppscotch-backend/src/types/InfraConfig.ts @@ -1,7 +1,16 @@ export enum InfraConfigEnum { + MAILER_SMTP_ENABLE = 'MAILER_SMTP_ENABLE', + MAILER_USE_CUSTOM_CONFIGS = 'MAILER_USE_CUSTOM_CONFIGS', MAILER_SMTP_URL = 'MAILER_SMTP_URL', MAILER_ADDRESS_FROM = 'MAILER_ADDRESS_FROM', + MAILER_SMTP_HOST = 'MAILER_SMTP_HOST', + MAILER_SMTP_PORT = 'MAILER_SMTP_PORT', + MAILER_SMTP_SECURE = 'MAILER_SMTP_SECURE', + MAILER_SMTP_USER = 'MAILER_SMTP_USER', + MAILER_SMTP_PASSWORD = 'MAILER_SMTP_PASSWORD', + MAILER_TLS_REJECT_UNAUTHORIZED = 'MAILER_TLS_REJECT_UNAUTHORIZED', + GOOGLE_CLIENT_ID = 'GOOGLE_CLIENT_ID', GOOGLE_CLIENT_SECRET = 'GOOGLE_CLIENT_SECRET', GOOGLE_CALLBACK_URL = 'GOOGLE_CALLBACK_URL', @@ -32,4 +41,6 @@ export enum InfraConfigEnum { ALLOW_ANALYTICS_COLLECTION = 'ALLOW_ANALYTICS_COLLECTION', ANALYTICS_USER_ID = 'ANALYTICS_USER_ID', IS_FIRST_TIME_INFRA_SETUP = 'IS_FIRST_TIME_INFRA_SETUP', + + USER_HISTORY_STORE_ENABLED = 'USER_HISTORY_STORE_ENABLED', } diff --git a/packages/hoppscotch-backend/src/types/RESTError.ts b/packages/hoppscotch-backend/src/types/RESTError.ts index 367c51dc09..4aa7d1b6e3 100644 --- a/packages/hoppscotch-backend/src/types/RESTError.ts +++ b/packages/hoppscotch-backend/src/types/RESTError.ts @@ -5,6 +5,6 @@ import { HttpStatus } from '@nestjs/common'; ** Since its REST we need to return the HTTP status code along with the error message */ export type RESTError = { - message: string; + message: string | Record; statusCode: HttpStatus; }; diff --git a/packages/hoppscotch-backend/src/types/input-types.args.ts b/packages/hoppscotch-backend/src/types/input-types.args.ts index 14a324997c..414b60cf9f 100644 --- a/packages/hoppscotch-backend/src/types/input-types.args.ts +++ b/packages/hoppscotch-backend/src/types/input-types.args.ts @@ -1,4 +1,7 @@ import { ArgsType, Field, ID, InputType } from '@nestjs/graphql'; +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsNotEmpty, IsOptional } from 'class-validator'; @ArgsType() @InputType() @@ -21,6 +24,10 @@ export class PaginationArgs { @ArgsType() @InputType() export class OffsetPaginationArgs { + @IsOptional() + @IsNotEmpty() + @Type(() => Number) + @ApiPropertyOptional() @Field({ nullable: true, defaultValue: 0, @@ -28,6 +35,10 @@ export class OffsetPaginationArgs { }) skip: number; + @IsOptional() + @IsNotEmpty() + @Type(() => Number) + @ApiPropertyOptional() @Field({ nullable: true, defaultValue: 10, diff --git a/packages/hoppscotch-backend/src/user-collection/user-collection.resolver.ts b/packages/hoppscotch-backend/src/user-collection/user-collection.resolver.ts index be0c3530ef..df530e3d79 100644 --- a/packages/hoppscotch-backend/src/user-collection/user-collection.resolver.ts +++ b/packages/hoppscotch-backend/src/user-collection/user-collection.resolver.ts @@ -16,6 +16,7 @@ import { AuthUser } from 'src/types/AuthUser'; import { UserCollectionService } from './user-collection.service'; import { UserCollection, + UserCollectionDuplicatedData, UserCollectionExportJSONData, UserCollectionRemovedData, UserCollectionReorderData, @@ -185,6 +186,30 @@ export class UserCollectionResolver { return jsonString.right; } + @Query(() => String, { + description: + 'Returns a JSON string of all the contents of a User Collection', + }) + @UseGuards(GqlAuthGuard) + async exportUserCollectionToJSON( + @GqlUser() user: AuthUser, + @Args({ + type: () => ID, + name: 'collectionID', + description: 'ID of the user collection', + }) + collectionID: string, + ) { + const jsonString = + await this.userCollectionService.exportUserCollectionToJSONObject( + user.uid, + collectionID, + ); + + if (E.isLeft(jsonString)) throwErr(jsonString.left as string); + return JSON.stringify(jsonString.right); + } + // Mutations @Mutation(() => UserCollection, { description: 'Creates root REST user collection(no parent user collection)', @@ -390,6 +415,36 @@ export class UserCollectionResolver { return updatedUserCollection.right; } + @Mutation(() => Boolean, { + description: 'Duplicate a User Collection', + }) + @UseGuards(GqlAuthGuard) + async duplicateUserCollection( + @GqlUser() user: AuthUser, + @Args({ + name: 'collectionID', + description: 'ID of the collection', + }) + collectionID: string, + @Args({ + name: 'reqType', + description: 'Type of UserCollection', + type: () => ReqType, + }) + reqType: ReqType, + ) { + const duplicatedUserCollection = + await this.userCollectionService.duplicateUserCollection( + collectionID, + user.uid, + reqType, + ); + + if (E.isLeft(duplicatedUserCollection)) + throwErr(duplicatedUserCollection.left); + return duplicatedUserCollection.right; + } + // Subscriptions @Subscription(() => UserCollection, { description: 'Listen for User Collection Creation', @@ -440,4 +495,14 @@ export class UserCollectionResolver { userCollectionOrderUpdated(@GqlUser() user: AuthUser) { return this.pubSub.asyncIterator(`user_coll/${user.uid}/order_updated`); } + + @Subscription(() => UserCollectionDuplicatedData, { + description: 'Listen to when a User Collection has been duplicated', + resolve: (value) => value, + }) + @SkipThrottle() + @UseGuards(GqlAuthGuard) + userCollectionDuplicated(@GqlUser() user: AuthUser) { + return this.pubSub.asyncIterator(`user_coll/${user.uid}/duplicated`); + } } diff --git a/packages/hoppscotch-backend/src/user-collection/user-collection.service.spec.ts b/packages/hoppscotch-backend/src/user-collection/user-collection.service.spec.ts index ceb8f3b450..99a81636c7 100644 --- a/packages/hoppscotch-backend/src/user-collection/user-collection.service.spec.ts +++ b/packages/hoppscotch-backend/src/user-collection/user-collection.service.spec.ts @@ -38,6 +38,8 @@ const user: AuthUser = { photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute', isAdmin: false, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, currentGQLSession: {}, currentRESTSession: {}, diff --git a/packages/hoppscotch-backend/src/user-collection/user-collection.service.ts b/packages/hoppscotch-backend/src/user-collection/user-collection.service.ts index 06128e0fdd..4366caf43d 100644 --- a/packages/hoppscotch-backend/src/user-collection/user-collection.service.ts +++ b/packages/hoppscotch-backend/src/user-collection/user-collection.service.ts @@ -23,9 +23,14 @@ import { Prisma, UserCollection, ReqType as DBReqType } from '@prisma/client'; import { UserCollection as UserCollectionModel, UserCollectionExportJSONData, + UserCollectionDuplicatedData, } from './user-collections.model'; import { ReqType } from 'src/types/RequestTypes'; -import { isValidLength, stringToJson } from 'src/utils'; +import { + isValidLength, + stringToJson, + transformCollectionData, +} from 'src/utils'; import { CollectionFolder } from 'src/types/CollectionFolder'; @Injectable() @@ -43,13 +48,15 @@ export class UserCollectionService { * @returns UserCollection model */ private cast(collection: UserCollection) { + const data = transformCollectionData(collection.data); + return { id: collection.id, title: collection.title, type: collection.type, parentID: collection.parentID, userID: collection.userUid, - data: !collection.data ? null : JSON.stringify(collection.data), + data, }; } @@ -829,7 +836,7 @@ export class UserCollectionService { * @param collectionID The Collection ID * @returns A JSON string containing all the contents of a collection */ - private async exportUserCollectionToJSONObject( + async exportUserCollectionToJSONObject( userUID: string, collectionID: string, ): Promise | E.Right> { @@ -871,6 +878,8 @@ export class UserCollectionService { }, }); + const data = transformCollectionData(collection.right.data); + const result: CollectionFolder = { id: collection.right.id, name: collection.right.title, @@ -882,7 +891,7 @@ export class UserCollectionService { ...(x.request as Record), // type casting x.request of type Prisma.JSONValue to an object to enable spread }; }), - data: JSON.stringify(collection.right.data), + data, }; return E.right(result); @@ -1027,6 +1036,7 @@ export class UserCollectionService { userID: string, destCollectionID: string | null, reqType: DBReqType, + isCollectionDuplication = false, ) { // Check to see if jsonString is valid const collectionsList = stringToJson(jsonString); @@ -1079,9 +1089,24 @@ export class UserCollectionService { ), ); - userCollections.forEach((collection) => - this.pubsub.publish(`user_coll/${userID}/created`, this.cast(collection)), - ); + if (isCollectionDuplication) { + const collectionData = await this.fetchCollectionData( + userCollections[0].id, + ); + if (E.isRight(collectionData)) { + this.pubsub.publish( + `user_coll/${userID}/duplicated`, + collectionData.right, + ); + } + } else { + userCollections.forEach((collection) => + this.pubsub.publish( + `user_coll/${userID}/created`, + this.cast(collection), + ), + ); + } return E.right(true); } @@ -1138,4 +1163,102 @@ export class UserCollectionService { return E.left(USER_COLL_NOT_FOUND); } } + + /** + * Duplicate a User Collection + * + * @param collectionID The Collection ID + * @returns Boolean of duplication status + */ + async duplicateUserCollection( + collectionID: string, + userID: string, + reqType: DBReqType, + ) { + const collection = await this.getUserCollection(collectionID); + if (E.isLeft(collection)) return E.left(USER_COLL_NOT_FOUND); + + if (collection.right.userUid !== userID) return E.left(USER_NOT_OWNER); + if (collection.right.type !== reqType) + return E.left(USER_COLL_NOT_SAME_TYPE); + + const collectionJSONObject = await this.exportUserCollectionToJSONObject( + collection.right.userUid, + collectionID, + ); + if (E.isLeft(collectionJSONObject)) + return E.left(collectionJSONObject.left); + + const result = await this.importCollectionsFromJSON( + JSON.stringify([ + { + ...collectionJSONObject.right, + name: `${collection.right.title} - Duplicate`, + }, + ]), + userID, + collection.right.parentID, + reqType, + true, + ); + if (E.isLeft(result)) return E.left(result.left as string); + + return E.right(true); + } + + /** + * Generates a JSON containing all the contents of a collection + * + * @param collection Collection whose details we want to fetch + * @returns A JSON string containing all the contents of a collection + */ + private async fetchCollectionData( + collectionID: string, + ): Promise | E.Right> { + const collection = await this.getUserCollection(collectionID); + if (E.isLeft(collection)) return E.left(collection.left); + + const { id, title, data, type, parentID, userUid } = collection.right; + const orderIndex = 'asc'; + + const [childCollections, requests] = await Promise.all([ + this.prisma.userCollection.findMany({ + where: { parentID: id }, + orderBy: { orderIndex }, + }), + this.prisma.userRequest.findMany({ + where: { collectionID: id }, + orderBy: { orderIndex }, + }), + ]); + + const childCollectionDataList = await Promise.all( + childCollections.map(({ id }) => this.fetchCollectionData(id)), + ); + + const failedChildData = childCollectionDataList.find(E.isLeft); + if (failedChildData) return E.left(failedChildData.left); + + const childCollectionsJSONStr = JSON.stringify( + (childCollectionDataList as E.Right[]).map( + (childCollection) => childCollection.right, + ), + ); + + const transformedRequests = requests.map((requestObj) => ({ + ...requestObj, + request: JSON.stringify(requestObj.request), + })); + + return E.right({ + id, + title, + data, + type, + parentID, + userID: userUid, + childCollections: childCollectionsJSONStr, + requests: transformedRequests, + }); + } } diff --git a/packages/hoppscotch-backend/src/user-collection/user-collections.model.ts b/packages/hoppscotch-backend/src/user-collection/user-collections.model.ts index 89c26861c8..d0f7717fc2 100644 --- a/packages/hoppscotch-backend/src/user-collection/user-collections.model.ts +++ b/packages/hoppscotch-backend/src/user-collection/user-collections.model.ts @@ -1,5 +1,7 @@ import { ObjectType, Field, ID, registerEnumType } from '@nestjs/graphql'; +import { User } from '@prisma/client'; import { ReqType } from 'src/types/RequestTypes'; +import { UserRequest } from 'src/user-request/user-request.model'; @ObjectType() export class UserCollection { @@ -73,3 +75,52 @@ export class UserCollectionExportJSONData { }) collectionType: ReqType; } + +@ObjectType() +export class UserCollectionDuplicatedData { + @Field(() => ID, { + description: 'ID of the user collection', + }) + id: string; + + @Field({ + description: 'Displayed title of the user collection', + }) + title: string; + + @Field({ + description: 'JSON string representing the collection data', + nullable: true, + }) + data: string; + + @Field(() => ReqType, { + description: 'Type of the user collection', + }) + type: ReqType; + + @Field({ + description: 'Parent ID of the duplicated User Collection', + nullable: true, + }) + parentID: string | null; + + @Field({ + description: 'User ID of the duplicated User Collection', + }) + userID: string; + + @Field({ + description: 'Child collections of the duplicated User Collection', + }) + childCollections: string; + + @Field(() => [UserRequest], { + description: 'Requests of the duplicated User Collection', + }) + requests: UserRequest[]; +} + +registerEnumType(ReqType, { + name: 'CollType', +}); diff --git a/packages/hoppscotch-backend/src/user-history/user-history-feature-flag.guard.ts b/packages/hoppscotch-backend/src/user-history/user-history-feature-flag.guard.ts new file mode 100644 index 0000000000..d878b4a293 --- /dev/null +++ b/packages/hoppscotch-backend/src/user-history/user-history-feature-flag.guard.ts @@ -0,0 +1,21 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { USER_HISTORY_FEATURE_FLAG_DISABLED } from 'src/errors'; +import { InfraConfigService } from 'src/infra-config/infra-config.service'; +import { throwErr } from 'src/utils'; +import * as E from 'fp-ts/Either'; +import { ServiceStatus } from 'src/infra-config/helper'; + +@Injectable() +export class UserHistoryFeatureFlagGuard implements CanActivate { + constructor(private readonly infraConfigService: InfraConfigService) {} + + async canActivate(context: ExecutionContext): Promise { + const isEnabled = await this.infraConfigService.isUserHistoryEnabled(); + if (E.isLeft(isEnabled)) throwErr(isEnabled.left); + + if (isEnabled.right.value !== ServiceStatus.ENABLE) + throwErr(USER_HISTORY_FEATURE_FLAG_DISABLED); + + return true; + } +} diff --git a/packages/hoppscotch-backend/src/user-history/user-history.module.ts b/packages/hoppscotch-backend/src/user-history/user-history.module.ts index aaa20bfec4..ce2d6884dd 100644 --- a/packages/hoppscotch-backend/src/user-history/user-history.module.ts +++ b/packages/hoppscotch-backend/src/user-history/user-history.module.ts @@ -5,9 +5,10 @@ import { UserModule } from '../user/user.module'; import { UserHistoryUserResolver } from './user.resolver'; import { UserHistoryResolver } from './user-history.resolver'; import { UserHistoryService } from './user-history.service'; +import { InfraConfigModule } from 'src/infra-config/infra-config.module'; @Module({ - imports: [PrismaModule, PubSubModule, UserModule], + imports: [PrismaModule, PubSubModule, UserModule, InfraConfigModule], providers: [UserHistoryResolver, UserHistoryService, UserHistoryUserResolver], exports: [UserHistoryService], }) diff --git a/packages/hoppscotch-backend/src/user-history/user-history.resolver.ts b/packages/hoppscotch-backend/src/user-history/user-history.resolver.ts index a07b26f837..13551a1192 100644 --- a/packages/hoppscotch-backend/src/user-history/user-history.resolver.ts +++ b/packages/hoppscotch-backend/src/user-history/user-history.resolver.ts @@ -11,8 +11,9 @@ import { throwErr } from '../utils'; import * as E from 'fp-ts/Either'; import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard'; import { SkipThrottle } from '@nestjs/throttler'; +import { UserHistoryFeatureFlagGuard } from './user-history-feature-flag.guard'; -@UseGuards(GqlThrottlerGuard) +@UseGuards(GqlThrottlerGuard, UserHistoryFeatureFlagGuard) @Resolver() export class UserHistoryResolver { constructor( @@ -156,4 +157,14 @@ export class UserHistoryResolver { userHistoryDeletedMany(@GqlUser() user: User) { return this.pubsub.asyncIterator(`user_history/${user.uid}/deleted_many`); } + + @Subscription(() => Boolean, { + description: 'Listen for All User History deleted', + resolve: (value) => value, + }) + @SkipThrottle() + @UseGuards(GqlAuthGuard) + userHistoryAllDeleted() { + return this.pubsub.asyncIterator(`user_history/all/deleted`); + } } diff --git a/packages/hoppscotch-backend/src/user-history/user-history.service.spec.ts b/packages/hoppscotch-backend/src/user-history/user-history.service.spec.ts index e53563926d..c9ccba49ec 100644 --- a/packages/hoppscotch-backend/src/user-history/user-history.service.spec.ts +++ b/packages/hoppscotch-backend/src/user-history/user-history.service.spec.ts @@ -493,6 +493,30 @@ describe('UserHistoryService', () => { ); }); }); + describe('deleteAllHistories', () => { + test('Should resolve right and delete all user history', async () => { + mockPrisma.userHistory.deleteMany.mockResolvedValueOnce({ + count: 2, + }); + + return expect(await userHistoryService.deleteAllHistories()).toEqualRight( + true, + ); + }); + test('Should publish all user history delete event', async () => { + mockPrisma.userHistory.deleteMany.mockResolvedValueOnce({ + count: 2, + }); + + await userHistoryService.deleteAllHistories(); + + expect(mockPubSub.publish).toHaveBeenCalledTimes(1); + return expect(mockPubSub.publish).toHaveBeenCalledWith( + `user_history/all/deleted`, + true, + ); + }); + }); describe('validateReqType', () => { test('Should resolve right when a valid REST ReqType is provided', async () => { return expect(userHistoryService.validateReqType('REST')).toEqualRight( diff --git a/packages/hoppscotch-backend/src/user-history/user-history.service.ts b/packages/hoppscotch-backend/src/user-history/user-history.service.ts index ba643c4a6b..19d65ae4f1 100644 --- a/packages/hoppscotch-backend/src/user-history/user-history.service.ts +++ b/packages/hoppscotch-backend/src/user-history/user-history.service.ts @@ -6,6 +6,7 @@ import { ReqType } from 'src/types/RequestTypes'; import * as E from 'fp-ts/Either'; import * as O from 'fp-ts/Option'; import { + USER_HISTORY_DELETION_FAILED, USER_HISTORY_INVALID_REQ_TYPE, USER_HISTORY_NOT_FOUND, } from '../errors'; @@ -188,6 +189,21 @@ export class UserHistoryService { return E.right(deletionInfo); } + /** + * Delete all user history from DB + * @returns a boolean + */ + async deleteAllHistories() { + try { + await this.prisma.userHistory.deleteMany(); + } catch (error) { + return E.left(USER_HISTORY_DELETION_FAILED); + } + + this.pubsub.publish('user_history/all/deleted', true); + return E.right(true); + } + /** * Fetch a user history based on history ID. * @param id User History ID diff --git a/packages/hoppscotch-backend/src/user-request/user-request.service.spec.ts b/packages/hoppscotch-backend/src/user-request/user-request.service.spec.ts index 2ff0db6faa..c5adb0d278 100644 --- a/packages/hoppscotch-backend/src/user-request/user-request.service.spec.ts +++ b/packages/hoppscotch-backend/src/user-request/user-request.service.spec.ts @@ -41,6 +41,8 @@ const user: AuthUser = { photoURL: 'https://example.com/photo.png', isAdmin: false, refreshToken: null, + lastLoggedOn: new Date(), + lastActiveOn: new Date(), createdOn: new Date(), currentGQLSession: null, currentRESTSession: null, diff --git a/packages/hoppscotch-backend/src/user-settings/user-settings.service.spec.ts b/packages/hoppscotch-backend/src/user-settings/user-settings.service.spec.ts index 5474894076..909f55a2bd 100644 --- a/packages/hoppscotch-backend/src/user-settings/user-settings.service.spec.ts +++ b/packages/hoppscotch-backend/src/user-settings/user-settings.service.spec.ts @@ -27,6 +27,8 @@ const user: AuthUser = { refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', currentGQLSession: {}, currentRESTSession: {}, + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, }; diff --git a/packages/hoppscotch-backend/src/user/user.model.ts b/packages/hoppscotch-backend/src/user/user.model.ts index 6889e93dbb..ffe1e6b626 100644 --- a/packages/hoppscotch-backend/src/user/user.model.ts +++ b/packages/hoppscotch-backend/src/user/user.model.ts @@ -30,6 +30,18 @@ export class User { }) isAdmin: boolean; + @Field({ + nullable: true, + description: 'Date when the user last logged in', + }) + lastLoggedOn: Date; + + @Field({ + nullable: true, + description: 'Date when the user last interacted with the app', + }) + lastActiveOn: Date; + @Field({ description: 'Date when the user account was created', }) diff --git a/packages/hoppscotch-backend/src/user/user.service.spec.ts b/packages/hoppscotch-backend/src/user/user.service.spec.ts index b5093831c8..8f9b7e63a9 100644 --- a/packages/hoppscotch-backend/src/user/user.service.spec.ts +++ b/packages/hoppscotch-backend/src/user/user.service.spec.ts @@ -42,6 +42,8 @@ const user: AuthUser = { currentRESTSession: {}, currentGQLSession: {}, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, }; @@ -54,6 +56,8 @@ const adminUser: AuthUser = { currentRESTSession: {}, currentGQLSession: {}, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, }; @@ -67,6 +71,8 @@ const users: AuthUser[] = [ currentRESTSession: {}, currentGQLSession: {}, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, }, { @@ -78,6 +84,8 @@ const users: AuthUser[] = [ currentRESTSession: {}, currentGQLSession: {}, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, }, { @@ -89,6 +97,8 @@ const users: AuthUser[] = [ currentRESTSession: {}, currentGQLSession: {}, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, }, ]; @@ -103,6 +113,8 @@ const adminUsers: AuthUser[] = [ currentRESTSession: {}, currentGQLSession: {}, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, }, { @@ -114,6 +126,8 @@ const adminUsers: AuthUser[] = [ currentRESTSession: {}, currentGQLSession: {}, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, }, { @@ -125,6 +139,8 @@ const adminUsers: AuthUser[] = [ currentRESTSession: {}, currentGQLSession: {}, refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', + lastLoggedOn: currentTime, + lastActiveOn: currentTime, createdOn: currentTime, }, ]; @@ -137,6 +153,14 @@ const exampleSSOProfileData = { photos: 'https://en.wikipedia.org/wiki/Dwight_Schrute', }; +beforeAll(() => { + process.env.DATA_ENCRYPTION_KEY = '12345678901234567890123456789012'; +}); + +afterAll(() => { + delete process.env.DATA_ENCRYPTION_KEY; // Clean up after tests +}); + beforeEach(() => { mockReset(mockPrisma); mockPubSub.publish.mockClear(); @@ -149,7 +173,7 @@ beforeEach(() => { describe('UserService', () => { describe('findUserByEmail', () => { test('should successfully return a valid user given a valid email', async () => { - mockPrisma.user.findUniqueOrThrow.mockResolvedValueOnce(user); + mockPrisma.user.findFirst.mockResolvedValueOnce(user); const result = await userService.findUserByEmail( 'dwight@dundermifflin.com', @@ -158,7 +182,7 @@ describe('UserService', () => { }); test('should return a null user given a invalid email', async () => { - mockPrisma.user.findUniqueOrThrow.mockRejectedValueOnce('NotFoundError'); + mockPrisma.user.findFirst.mockResolvedValueOnce(null); const result = await userService.findUserByEmail('jim@dundermifflin.com'); expect(result).resolves.toBeNone; @@ -495,6 +519,26 @@ describe('UserService', () => { }); }); + describe('updateUserLastLoggedOn', () => { + test('should resolve right and update user last logged on', async () => { + const currentTime = new Date(); + mockPrisma.user.update.mockResolvedValueOnce({ + ...user, + lastLoggedOn: currentTime, + }); + + const result = await userService.updateUserLastLoggedOn(user.uid); + expect(result).toEqualRight(true); + }); + + test('should resolve left and error when invalid user uid is passed', async () => { + mockPrisma.user.update.mockRejectedValueOnce('NotFoundError'); + + const result = await userService.updateUserLastLoggedOn('invalidUserUid'); + expect(result).toEqualLeft(USER_NOT_FOUND); + }); + }); + describe('fetchAllUsers', () => { test('should resolve right and return 20 users when cursor is null', async () => { mockPrisma.user.findMany.mockResolvedValueOnce(users); diff --git a/packages/hoppscotch-backend/src/user/user.service.ts b/packages/hoppscotch-backend/src/user/user.service.ts index 26018894bf..c3162e85a7 100644 --- a/packages/hoppscotch-backend/src/user/user.service.ts +++ b/packages/hoppscotch-backend/src/user/user.service.ts @@ -16,10 +16,12 @@ import { import { SessionType, User } from './user.model'; import { USER_UPDATE_FAILED } from 'src/errors'; import { PubSubService } from 'src/pubsub/pubsub.service'; -import { stringToJson, taskEitherValidateArraySeq } from 'src/utils'; +import { encrypt, stringToJson, taskEitherValidateArraySeq } from 'src/utils'; import { UserDataHandler } from './user.data.handler'; import { User as DbUser } from '@prisma/client'; import { OffsetPaginationArgs } from 'src/types/input-types.args'; +import { GetUserWorkspacesResponse } from 'src/infra-token/request-response.dto'; +import { TeamMemberRole } from 'src/team/team.model'; @Injectable() export class UserService { @@ -62,16 +64,16 @@ export class UserService { * @returns Option of found User */ async findUserByEmail(email: string): Promise> { - try { - const user = await this.prisma.user.findUniqueOrThrow({ - where: { - email: email, + const user = await this.prisma.user.findFirst({ + where: { + email: { + equals: email, + mode: 'insensitive', }, - }); - return O.some(user); - } catch (error) { - return O.none; - } + }, + }); + if (!user) return O.none; + return O.some(user); } /** @@ -114,7 +116,7 @@ export class UserService { * @param userUid User uid * @returns Either of User with updated refreshToken */ - async UpdateUserRefreshToken(refreshTokenHash: string, userUid: string) { + async updateUserRefreshToken(refreshTokenHash: string, userUid: string) { try { const user = await this.prisma.user.update({ where: { @@ -174,6 +176,7 @@ export class UserService { displayName: userDisplayName, email: profile.emails[0].value, photoURL: userPhotoURL, + lastLoggedOn: new Date(), providerAccounts: { create: { provider: profile.provider, @@ -207,8 +210,8 @@ export class UserService { data: { provider: profile.provider, providerAccountId: profile.id, - providerRefreshToken: refreshToken ? refreshToken : null, - providerAccessToken: accessToken ? accessToken : null, + providerRefreshToken: refreshToken ? encrypt(refreshToken) : null, + providerAccessToken: accessToken ? encrypt(accessToken) : null, user: { connect: { uid: user.uid, @@ -221,7 +224,7 @@ export class UserService { } /** - * Update User displayName and photoURL + * Update User displayName and photoURL when logged in via a SSO provider * * @param user User object * @param profile Data received from SSO provider on the users account @@ -236,6 +239,7 @@ export class UserService { data: { displayName: !profile.displayName ? null : profile.displayName, photoURL: !profile.photos ? null : profile.photos[0].value, + lastLoggedOn: new Date(), }, }); return E.right(updatedUser); @@ -289,7 +293,7 @@ export class UserService { } /** - * Update a user's data + * Update a user's displayName * @param userUID User UID * @param displayName User's displayName * @returns a Either of User or error @@ -316,6 +320,38 @@ export class UserService { } } + /** + * Update user's lastLoggedOn timestamp + * @param userUID User UID + */ + async updateUserLastLoggedOn(userUid: string) { + try { + await this.prisma.user.update({ + where: { uid: userUid }, + data: { lastLoggedOn: new Date() }, + }); + return E.right(true); + } catch (e) { + return E.left(USER_NOT_FOUND); + } + } + + /** + * Update user's lastActiveOn timestamp + * @param userUID User UID + */ + async updateUserLastActiveOn(userUid: string) { + try { + await this.prisma.user.update({ + where: { uid: userUid }, + data: { lastActiveOn: new Date() }, + }); + return E.right(true); + } catch (e) { + return E.left(USER_NOT_FOUND); + } + } + /** * Validate and parse currentRESTSession and currentGQLSession * @param sessionData string of the session @@ -564,4 +600,52 @@ export class UserService { return E.right(true); } + + async fetchUserWorkspaces(userUid: string) { + const user = await this.prisma.user.findUnique({ where: { uid: userUid } }); + if (!user) return E.left(USER_NOT_FOUND); + + const team = await this.prisma.team.findMany({ + where: { + members: { + some: { + userUid, + }, + }, + }, + include: { + members: { + select: { + userUid: true, + role: true, + }, + }, + }, + }); + + const workspaces: GetUserWorkspacesResponse[] = []; + team.forEach((t) => { + const ownerCount = t.members.filter( + (m) => m.role === TeamMemberRole.OWNER, + ).length; + const editorCount = t.members.filter( + (m) => m.role === TeamMemberRole.EDITOR, + ).length; + const viewerCount = t.members.filter( + (m) => m.role === TeamMemberRole.VIEWER, + ).length; + const memberCount = t.members.length; + + workspaces.push({ + id: t.id, + name: t.name, + role: t.members.find((m) => m.userUid === userUid)?.role, + owner_count: ownerCount, + editor_count: editorCount, + viewer_count: viewerCount, + member_count: memberCount, + }); + }); + return E.right(workspaces); + } } diff --git a/packages/hoppscotch-backend/src/utils.ts b/packages/hoppscotch-backend/src/utils.ts index f201a37b3e..23afc3fa33 100644 --- a/packages/hoppscotch-backend/src/utils.ts +++ b/packages/hoppscotch-backend/src/utils.ts @@ -1,22 +1,24 @@ import { ExecutionContext, HttpException } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { GqlExecutionContext } from '@nestjs/graphql'; +import { Prisma } from '@prisma/client'; +import * as A from 'fp-ts/Array'; +import * as E from 'fp-ts/Either'; import { pipe } from 'fp-ts/lib/function'; import * as O from 'fp-ts/Option'; -import * as TE from 'fp-ts/TaskEither'; import * as T from 'fp-ts/Task'; -import * as E from 'fp-ts/Either'; -import * as A from 'fp-ts/Array'; -import { TeamMemberRole } from './team/team.model'; -import { User } from './user/user.model'; +import * as TE from 'fp-ts/TaskEither'; +import { AuthProvider } from './auth/helper'; import { ENV_EMPTY_AUTH_PROVIDERS, ENV_NOT_FOUND_KEY_AUTH_PROVIDERS, + ENV_NOT_FOUND_KEY_DATA_ENCRYPTION_KEY, ENV_NOT_SUPPORT_AUTH_PROVIDERS, JSON_INVALID, } from './errors'; -import { AuthProvider } from './auth/helper'; +import { TeamMemberRole } from './team/team.model'; import { RESTError } from './types/RESTError'; +import * as crypto from 'crypto'; /** * A workaround to throw an exception in an expression. @@ -114,6 +116,17 @@ export const getGqlArg = ( ); /** + * To the daring adventurer who has stumbled upon this relic of code... welcome. + * Many have gazed upon its depths, yet few have returned with answers. + * I could have deleted it, but that felt... too easy, too final. + * + * If you're still reading, perhaps you're the one destined to unravel its secrets. + * Or, maybe you're like me—content to let it linger, a puzzle for the ages. + * The choice is yours, but beware... once you start, there is no turning back. + * + * PLEASE, NO ONE KNOWS HOW THIS WORKS... + * -- Balu, whispering from the great beyond... probably still trying to understand this damn thing. + * * Sequences an array of TaskEither values while maintaining an array of all the error values * @param arr Array of TaskEithers * @returns A TaskEither saying all the errors possible on the left or all the success values on the right @@ -286,3 +299,87 @@ export function escapeSqlLikeString(str: string) { } }); } + +/** + * Calculate the expiration date of the token + * + * @param expiresOn Number of days the token is valid for + * @returns Date object of the expiration date + */ +export function calculateExpirationDate(expiresOn: null | number) { + if (expiresOn === null) return null; + return new Date(Date.now() + expiresOn * 24 * 60 * 60 * 1000); +} + +/* + * Transforms the collection level properties (authorization & headers) under the `data` field. + * Preserves `null` values and prevents duplicate stringification. + * + * @param {Prisma.JsonValue} collectionData - The team collection data to transform. + * @returns {string | null} The transformed team collection data as a string. + */ +export function transformCollectionData( + collectionData: Prisma.JsonValue, +): string | null { + if (!collectionData) { + return null; + } + + return typeof collectionData === 'string' + ? collectionData + : JSON.stringify(collectionData); +} + +// Encrypt and Decrypt functions. InfraConfig and Account table uses these functions to encrypt and decrypt the data. +const ENCRYPTION_ALGORITHM = 'aes-256-cbc'; + +/** + * Encrypts a text using a key + * @param text The text to encrypt + * @param key The key to use for encryption + * @returns The encrypted text + */ +export function encrypt(text: string, key = process.env.DATA_ENCRYPTION_KEY) { + if (!key) throw new Error(ENV_NOT_FOUND_KEY_DATA_ENCRYPTION_KEY); + + if (text === null || text === undefined) return text; + + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv( + ENCRYPTION_ALGORITHM, + Buffer.from(key), + iv, + ); + let encrypted = cipher.update(text); + encrypted = Buffer.concat([encrypted, cipher.final()]); + return iv.toString('hex') + ':' + encrypted.toString('hex'); +} + +/** + * Decrypts a text using a key + * @param text The text to decrypt + * @param key The key to use for decryption + * @returns The decrypted text + */ +export function decrypt( + encryptedData: string, + key = process.env.DATA_ENCRYPTION_KEY, +) { + if (!key) throw new Error(ENV_NOT_FOUND_KEY_DATA_ENCRYPTION_KEY); + + if (encryptedData === null || encryptedData === undefined) { + return encryptedData; + } + + const textParts = encryptedData.split(':'); + const iv = Buffer.from(textParts.shift(), 'hex'); + const encryptedText = Buffer.from(textParts.join(':'), 'hex'); + const decipher = crypto.createDecipheriv( + ENCRYPTION_ALGORITHM, + Buffer.from(key), + iv, + ); + let decrypted = decipher.update(encryptedText); + decrypted = Buffer.concat([decrypted, decipher.final()]); + return decrypted.toString(); +} diff --git a/packages/hoppscotch-backend/tsconfig.build.json b/packages/hoppscotch-backend/tsconfig.build.json index 64f86c6bd2..ffc7919466 100644 --- a/packages/hoppscotch-backend/tsconfig.build.json +++ b/packages/hoppscotch-backend/tsconfig.build.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", - "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"], + "compilerOptions": { + "declaration": false, + "sourceMap": false + } } diff --git a/packages/hoppscotch-cli/README.md b/packages/hoppscotch-cli/README.md index 64fce67371..28a1efdb57 100644 --- a/packages/hoppscotch-cli/README.md +++ b/packages/hoppscotch-cli/README.md @@ -28,35 +28,76 @@ hopp [options or commands] arguments - Displays the help text 3. #### **`hopp test [options] `** + - Interactive CLI to accept Hoppscotch collection JSON path - Parses the collection JSON and executes each requests - Executes pre-request script. - Outputs the response of each request. - Executes and outputs test-script response. - #### Options: + #### Options: + + ##### `-e ` / `--env ` + + - Accepts path to env.json with contents in below format: + + ```json + { + "ENV1": "value1", + "ENV2": "value2" + } + ``` + + - You can now access those variables using `pw.env.get('')` + + Taking the above example, `pw.env.get("ENV1")` will return `"value1"` - ##### `-e ` / `--env ` + ##### `--iteration-count ` - - Accepts path to env.json with contents in below format: + - Accepts the number of iterations to run the collection - ```json - { - "ENV1":"value1", - "ENV2":"value2" - } - ``` + ##### `--iteration-data ` - - You can now access those variables using `pw.env.get('')` + - Accepts the path to a CSV file with contents in the below format: - Taking the above example, `pw.env.get("ENV1")` will return `"value1"` + ```text + key1,key2,key3 + value1,value2,value3 + value4,value5,value6 + ``` + + For every iteration the values will be replaced with the respective keys in the environment. For iteration 1 the value1,value2,value3 will be replaced and for iteration 2 value4,value5,value6 will be replaced and so on. ## Install -Install [@hoppscotch/cli](https://www.npmjs.com/package/@hoppscotch/cli) from npm by running: -``` -npm i -g @hoppscotch/cli -``` +- Before you install Hoppscotch CLI you need to make sure you have the dependencies it requires to run. + + - **Windows & macOS**: You will need `node-gyp` installed. Find instructions here: https://github.com/nodejs/node-gyp + - **Debian/Ubuntu derivatives**: + ```sh + sudo apt-get install python g++ build-essential + ``` + - **Alpine Linux**: + ```sh + sudo apk add python3 make g++ + ``` + - **Amazon Linux (AMI)** + ```sh + sudo yum install gcc72 gcc72-c++ + ``` + - **Arch Linux** + ```sh + sudo pacman -S make gcc python + ``` + - **RHEL/Fedora derivatives**: + ```sh + sudo dnf install python3 make gcc gcc-c++ zlib-devel brotli-devel openssl-devel libuv-devel + ``` + +- Once the dependencies are installed, install [@hoppscotch/cli](https://www.npmjs.com/package/@hoppscotch/cli) from npm by running: + ``` + npm i -g @hoppscotch/cli + ``` ## **Developing:** @@ -89,39 +130,39 @@ Please note we have a code of conduct, please follow it in all your interactions 1. After cloning the repository, execute the following commands: - ```bash - pnpm install - pnpm run build - ``` + ```bash + pnpm install + pnpm run build + ``` 2. In order to test locally, you can use two types of package linking: - 1. The 'pnpm exec' way (preferred since it does not hamper your original installation of the CLI): + 1. The 'pnpm exec' way (preferred since it does not hamper your original installation of the CLI): - ```bash - pnpm link @hoppscotch/cli + ```bash + pnpm link @hoppscotch/cli - // Then to use or test the CLI: - pnpm exec hopp + // Then to use or test the CLI: + pnpm exec hopp - // After testing, to remove the package linking: - pnpm rm @hoppscotch/cli - ``` + // After testing, to remove the package linking: + pnpm rm @hoppscotch/cli + ``` - 2. The 'global' way (warning: this might override the globally installed CLI, if exists): + 2. The 'global' way (warning: this might override the globally installed CLI, if exists): - ```bash - sudo pnpm link --global + ```bash + sudo pnpm link --global - // Then to use or test the CLI: - hopp + // Then to use or test the CLI: + hopp - // After testing, to remove the package linking: - sudo pnpm rm --global @hoppscotch/cli - ``` + // After testing, to remove the package linking: + sudo pnpm rm --global @hoppscotch/cli + ``` 3. To use the Typescript watch scripts: - ```bash - pnpm run dev - ``` + ```bash + pnpm run dev + ``` diff --git a/packages/hoppscotch-cli/bin/hopp.js b/packages/hoppscotch-cli/bin/hopp.js index 06750bef9f..57212501dd 100755 --- a/packages/hoppscotch-cli/bin/hopp.js +++ b/packages/hoppscotch-cli/bin/hopp.js @@ -1,6 +1,61 @@ #!/usr/bin/env node // * The entry point of the CLI +// @ts-check -import { cli } from "../dist/index.js"; +import chalk from "chalk"; +import { spawnSync } from "child_process"; +import fs from "fs"; +import { cloneDeep } from "lodash-es"; +import semver from "semver"; +import { fileURLToPath } from "url"; -cli(process.argv); +const highlightVersion = (version) => chalk.black.bgYellow(`v${version}`); + +const packageJsonPath = fileURLToPath( + new URL("../package.json", import.meta.url) +); +const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); + +const requiredNodeVersionRange = packageJson.engines?.node || ">=20"; + +// Extract the major version from the start of the range +const requiredNodeVersion = semver.major( + semver.minVersion(requiredNodeVersionRange) ?? "20" +); + +const currentNodeVersion = process.versions.node; + +// Last supported version of the CLI for Node.js v18 +const lastSupportedVersion = "0.11.1"; + +if (!semver.satisfies(currentNodeVersion, requiredNodeVersionRange)) { + console.error( + `${chalk.greenBright("Hoppscotch CLI")} requires Node.js ${highlightVersion(requiredNodeVersion)} or higher and you're on Node.js ${highlightVersion(currentNodeVersion)}.` + ); + + console.error( + `\nIf you prefer staying on Node.js ${highlightVersion("18")}, you can install the last supported version of the CLI:\n` + + `${chalk.green(`npm install -g @hoppscotch/cli@${lastSupportedVersion}`)}` + ); + process.exit(1); +} + +// Dynamically importing the module after the Node.js version check prevents errors due to unrecognized APIs in older Node.js versions +const { cli } = await import("../dist/index.js"); + +// As per isolated-vm documentation, we need to supply `--no-node-snapshot` for node >= 20 +// src: https://github.com/laverdet/isolated-vm?tab=readme-ov-file#requirements +if (!process.execArgv.includes("--no-node-snapshot")) { + const argCopy = cloneDeep(process.argv); + + // Replace first argument with --no-node-snapshot + // We can get argv[0] from process.argv0 + argCopy[0] = "--no-node-snapshot"; + + const result = spawnSync(process.argv0, argCopy, { stdio: "inherit" }); + + // Exit with the same status code as the spawned process + process.exit(result.status ?? 0); +} else { + cli(process.argv); +} diff --git a/packages/hoppscotch-cli/jest.config.ts b/packages/hoppscotch-cli/jest.config.ts deleted file mode 100644 index 39cb61bd50..0000000000 --- a/packages/hoppscotch-cli/jest.config.ts +++ /dev/null @@ -1,193 +0,0 @@ -/* - * For a detailed explanation regarding each configuration property, visit: - * https://jestjs.io/docs/configuration - */ - -module.exports = { - // All imported modules in your tests should be mocked automatically - // automock: false, - - // Stop running tests after `n` failures - // bail: 0, - - // The directory where Jest should store its cached dependency information - // cacheDirectory: "/tmp/jest_rs", - - // Automatically clear mock calls, instances and results before every test - clearMocks: true, - - // Indicates whether the coverage information should be collected while executing the test - // collectCoverage: true, - - // An array of glob patterns indicating a set of files for which coverage information should be collected - // collectCoverageFrom: undefined, - - // The directory where Jest should output its coverage files - // coverageDirectory: undefined, - - // An array of regexp pattern strings used to skip coverage collection - // coveragePathIgnorePatterns: [ - // "/node_modules/" - // ], - - // Indicates which provider should be used to instrument code for coverage - // coverageProvider: "babel", - - // A list of reporter names that Jest uses when writing coverage reports - // coverageReporters: [ - // "json", - // "text", - // "lcov", - // "clover" - // ], - - // An object that configures minimum threshold enforcement for coverage results - // coverageThreshold: undefined, - - // A path to a custom dependency extractor - // dependencyExtractor: undefined, - - // Make calling deprecated APIs throw helpful error messages - // errorOnDeprecated: false, - - // Force coverage collection from ignored files using an array of glob patterns - // forceCoverageMatch: [], - - // A path to a module which exports an async function that is triggered once before all test suites - // globalSetup: undefined, - - // A path to a module which exports an async function that is triggered once after all test suites - // globalTeardown: undefined, - - // A set of global variables that need to be available in all test environments - // globals: { - // 'ts-jest': { - // useESM: true, - // }, - // }, - - // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. - // maxWorkers: "50%", - - // An array of directory names to be searched recursively up from the requiring module's location - // moduleDirectories: [ - // "node_modules" - // ], - - // An array of file extensions your modules use - moduleFileExtensions: ["js", "ts", "json"], - - // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module - // moduleNameMapper: { - // '^(\\.{1,2}/.*)\\.js$': '$1', - // }, - - // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader - // modulePathIgnorePatterns: [], - - // Activates notifications for test results - // notify: false, - - // An enum that specifies notification mode. Requires { notify: true } - // notifyMode: "failure-change", - - // A preset that is used as a base for Jest's configuration - preset: "ts-jest/presets/js-with-babel", - - // Run tests from one or more projects - // projects: undefined, - - // Use this configuration option to add custom reporters to Jest - // reporters: undefined, - - // Automatically reset mock state before every test - // resetMocks: false, - - // Reset the module registry before running each individual test - // resetModules: false, - - // A path to a custom resolver - // resolver: undefined, - - // Automatically restore mock state and implementation before every test - // restoreMocks: false, - - // The root directory that Jest should scan for tests and modules within - // rootDir: undefined, - - // A list of paths to directories that Jest should use to search for files in - // roots: [ - // "" - // ], - - // Allows you to use a custom runner instead of Jest's default test runner - // runner: "jest-runner", - - // The paths to modules that run some code to configure or set up the testing environment before each test - // setupFiles: [], - - // A list of paths to modules that run some code to configure or set up the testing framework before each test - setupFilesAfterEnv: ["./jest.setup.ts"], - - // The number of seconds after which a test is considered as slow and reported as such in the results. - // slowTestThreshold: 5, - - // A list of paths to snapshot serializer modules Jest should use for snapshot testing - // snapshotSerializers: [], - - // The test environment that will be used for testing - testEnvironment: "node", - - // Options that will be passed to the testEnvironment - // testEnvironmentOptions: {}, - - // Adds a location field to test results - // testLocationInResults: false, - - // The glob patterns Jest uses to detect test files - testMatch: [ - // "**/__tests__/**/*.[jt]s?(x)", - "**/src/__tests__/commands/**/*.*.ts", - ], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - testPathIgnorePatterns: ["/node_modules/", "/dist/"], - - // The regexp pattern or array of patterns that Jest uses to detect test files - // testRegex: [], - - // This option allows the use of a custom results processor - // testResultsProcessor: undefined, - - // This option allows use of a custom test runner - // testRunner: "jest-circus/runner", - - // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href - // testURL: "http://localhost", - - // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" - // timers: "real", - - // A map from regular expressions to paths to transformers - transform: { - "^.+\\.ts$": "ts-jest", - }, - - // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation - // transformIgnorePatterns: [ - // "/node_modules/", - // "\\.pnp\\.[^\\/]+$" - // ], - - // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them - // unmockedModulePathPatterns: undefined, - - // Indicates whether each individual test should be reported during the run - verbose: true, - - // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode - // watchPathIgnorePatterns: [], - - // Whether to use watchman for file crawling - // watchman: true, -}; diff --git a/packages/hoppscotch-cli/jest.setup.ts b/packages/hoppscotch-cli/jest.setup.ts deleted file mode 100644 index 7a33702a40..0000000000 --- a/packages/hoppscotch-cli/jest.setup.ts +++ /dev/null @@ -1 +0,0 @@ -import "@relmify/jest-fp-ts"; diff --git a/packages/hoppscotch-cli/package.json b/packages/hoppscotch-cli/package.json index 33892bcd50..dac26e520a 100644 --- a/packages/hoppscotch-cli/package.json +++ b/packages/hoppscotch-cli/package.json @@ -1,6 +1,6 @@ { "name": "@hoppscotch/cli", - "version": "0.7.0", + "version": "0.20.1", "description": "A CLI to run Hoppscotch test scripts in CI environments.", "homepage": "https://hoppscotch.io", "type": "module", @@ -12,7 +12,7 @@ "access": "public" }, "engines": { - "node": ">=18" + "node": ">=20" }, "scripts": { "build": "pnpm exec tsup", @@ -20,9 +20,9 @@ "debugger": "node debugger.js 9999", "prepublish": "pnpm exec tsup", "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write", - "test": "pnpm run build && jest && rm -rf dist", + "test": "pnpm run build && vitest run", "do-typecheck": "pnpm exec tsc --noEmit", - "do-test": "pnpm test" + "do-test": "pnpm run test" }, "keywords": [ "cli", @@ -41,28 +41,32 @@ "license": "MIT", "private": false, "dependencies": { - "axios": "1.6.7", + "aws4fetch": "1.0.20", + "axios": "1.7.7", "chalk": "5.3.0", - "commander": "11.1.0", + "commander": "12.1.0", + "isolated-vm": "5.0.1", + "js-md5": "0.8.3", "lodash-es": "4.17.21", - "qs": "6.11.2", - "verzod": "0.2.2", - "zod": "3.22.4" + "papaparse": "5.4.1", + "qs": "6.13.0", + "verzod": "0.2.3", + "xmlbuilder2": "3.1.1", + "zod": "3.23.8" }, "devDependencies": { "@hoppscotch/data": "workspace:^", "@hoppscotch/js-sandbox": "workspace:^", "@relmify/jest-fp-ts": "2.1.1", - "@swc/core": "1.4.2", - "@types/jest": "29.5.12", "@types/lodash-es": "4.17.12", - "@types/qs": "6.9.12", - "fp-ts": "2.16.2", - "jest": "29.7.0", - "prettier": "3.2.5", + "@types/papaparse": "5.3.14", + "@types/qs": "6.9.16", + "fp-ts": "2.16.9", + "prettier": "3.3.3", "qs": "6.11.2", - "ts-jest": "29.1.2", - "tsup": "8.0.2", - "typescript": "5.3.3" + "semver": "7.6.3", + "tsup": "8.3.0", + "typescript": "5.6.3", + "vitest": "2.1.2" } -} \ No newline at end of file +} diff --git a/packages/hoppscotch-cli/setupFiles.ts b/packages/hoppscotch-cli/setupFiles.ts new file mode 100644 index 0000000000..0a1bcdc0b0 --- /dev/null +++ b/packages/hoppscotch-cli/setupFiles.ts @@ -0,0 +1,15 @@ +// Vitest doesn't work without globals +// Ref: https://github.com/relmify/jest-fp-ts/issues/11 + +import decodeMatchers from "@relmify/jest-fp-ts/dist/decodeMatchers"; +import eitherMatchers from "@relmify/jest-fp-ts/dist/eitherMatchers"; +import optionMatchers from "@relmify/jest-fp-ts/dist/optionMatchers"; +import theseMatchers from "@relmify/jest-fp-ts/dist/theseMatchers"; +import eitherOrTheseMatchers from "@relmify/jest-fp-ts/dist/eitherOrTheseMatchers"; +import { expect } from "vitest"; + +expect.extend(decodeMatchers.matchers); +expect.extend(eitherMatchers.matchers); +expect.extend(optionMatchers.matchers); +expect.extend(theseMatchers.matchers); +expect.extend(eitherOrTheseMatchers.matchers); diff --git a/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts b/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts deleted file mode 100644 index 25b3325d24..0000000000 --- a/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts +++ /dev/null @@ -1,345 +0,0 @@ -import { ExecException } from "child_process"; - -import { HoppErrorCode } from "../../types/errors"; -import { runCLI, getErrorCode, getTestJsonFilePath } from "../utils"; - -describe("Test `hopp test ` command:", () => { - describe("Argument parsing", () => { - test("Errors with the code `INVALID_ARGUMENT` for not supplying enough arguments", async () => { - const args = "test"; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("INVALID_ARGUMENT"); - }); - - test("Errors with the code `INVALID_ARGUMENT` for an invalid command", async () => { - const args = "invalid-arg"; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("INVALID_ARGUMENT"); - }); - }); - - describe("Supplied collection export file validations", () => { - test("Errors with the code `FILE_NOT_FOUND` if the supplied collection export file doesn't exist", async () => { - const args = "test notfound.json"; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("FILE_NOT_FOUND"); - }); - - test("Errors with the code UNKNOWN_ERROR if the supplied collection export file content isn't valid JSON", async () => { - const args = `test ${getTestJsonFilePath("malformed-coll.json", "collection")}`; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("UNKNOWN_ERROR"); - }); - - test("Errors with the code `MALFORMED_COLLECTION` if the supplied collection export file content is malformed", async () => { - const args = `test ${getTestJsonFilePath("malformed-coll-2.json", "collection")}`; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("MALFORMED_COLLECTION"); - }); - - test("Errors with the code `INVALID_FILE_TYPE` if the supplied collection export file doesn't end with the `.json` extension", async () => { - const args = `test ${getTestJsonFilePath("notjson-coll.txt", "collection")}`; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("INVALID_FILE_TYPE"); - }); - - test("Fails if the collection file includes scripts with incorrect API usage and failed assertions", async () => { - const args = `test ${getTestJsonFilePath("fails-coll.json", "collection")}`; - const { error } = await runCLI(args); - - expect(error).not.toBeNull(); - expect(error).toMatchObject({ - code: 1, - }); - }); - }); - - describe("Versioned entities", () => { - describe("Collections & Requests", () => { - const testFixtures = [ - { fileName: "coll-v1-req-v0.json", collVersion: 1, reqVersion: 0 }, - { fileName: "coll-v1-req-v1.json", collVersion: 1, reqVersion: 1 }, - { fileName: "coll-v2-req-v2.json", collVersion: 2, reqVersion: 2 }, - { fileName: "coll-v2-req-v3.json", collVersion: 2, reqVersion: 3 }, - ]; - - testFixtures.forEach(({ collVersion, fileName, reqVersion }) => { - test(`Successfully processes a supplied collection export file where the collection is based on the "v${collVersion}" schema and the request following the "v${reqVersion}" schema`, async () => { - const args = `test ${getTestJsonFilePath(fileName, "collection")}`; - const { error } = await runCLI(args); - - expect(error).toBeNull(); - }); - }); - }); - - describe("Environments", () => { - const testFixtures = [ - { fileName: "env-v0.json", version: 0 }, - { fileName: "env-v1.json", version: 1 }, - ]; - - testFixtures.forEach(({ fileName, version }) => { - test(`Successfully processes the supplied collection and environment export files where the environment is based on the "v${version}" schema`, async () => { - const ENV_PATH = getTestJsonFilePath(fileName, "environment"); - const args = `test ${getTestJsonFilePath("sample-coll.json", "collection")} --env ${ENV_PATH}`; - const { error } = await runCLI(args); - - expect(error).toBeNull(); - }); - }); - }); - }); - - test("Successfully processes a supplied collection export file of the expected format", async () => { - const args = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`; - const { error } = await runCLI(args); - - expect(error).toBeNull(); - }); - - test("Successfully inherits headers and authorization set at the root collection", async () => { - const args = `test ${getTestJsonFilePath( - "collection-level-headers-auth-coll.json", - "collection" - )}`; - const { error } = await runCLI(args); - - expect(error).toBeNull(); - }); - - test("Persists environment variables set in the pre-request script for consumption in the test script", async () => { - const args = `test ${getTestJsonFilePath( - "pre-req-script-env-var-persistence-coll.json", - "collection" - )}`; - const { error } = await runCLI(args); - - expect(error).toBeNull(); - }); -}); - -describe("Test `hopp test --env ` command:", () => { - describe("Supplied environment export file validations", () => { - const VALID_TEST_ARGS = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`; - - test("Errors with the code `INVALID_ARGUMENT` if no file is supplied", async () => { - const args = `${VALID_TEST_ARGS} --env`; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("INVALID_ARGUMENT"); - }); - - test("Errors with the code `INVALID_FILE_TYPE` if the supplied environment export file doesn't end with the `.json` extension", async () => { - const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath( - "notjson-coll.txt", - "collection" - )}`; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("INVALID_FILE_TYPE"); - }); - - test("Errors with the code `FILE_NOT_FOUND` if the supplied environment export file doesn't exist", async () => { - const args = `${VALID_TEST_ARGS} --env notfound.json`; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("FILE_NOT_FOUND"); - }); - - test("Errors with the code `MALFORMED_ENV_FILE` on supplying a malformed environment export file", async () => { - const ENV_PATH = getTestJsonFilePath( - "malformed-envs.json", - "environment" - ); - const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("MALFORMED_ENV_FILE"); - }); - - test("Errors with the code `BULK_ENV_FILE` on supplying an environment export file based on the bulk environment export format", async () => { - const ENV_PATH = getTestJsonFilePath("bulk-envs.json", "environment"); - const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("BULK_ENV_FILE"); - }); - }); - - test("Successfully resolves values from the supplied environment export file", async () => { - const TESTS_PATH = getTestJsonFilePath( - "env-flag-tests-coll.json", - "collection" - ); - const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment"); - const args = `test ${TESTS_PATH} --env ${ENV_PATH}`; - - const { error } = await runCLI(args); - expect(error).toBeNull(); - }); - - test("Successfully resolves environment variables referenced in the request body", async () => { - const COLL_PATH = getTestJsonFilePath( - "req-body-env-vars-coll.json", - "collection" - ); - const ENVS_PATH = getTestJsonFilePath( - "req-body-env-vars-envs.json", - "environment" - ); - const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; - - const { error } = await runCLI(args); - expect(error).toBeNull(); - }); - - test("Works with shorth `-e` flag", async () => { - const TESTS_PATH = getTestJsonFilePath( - "env-flag-tests-coll.json", - "collection" - ); - const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment"); - const args = `test ${TESTS_PATH} -e ${ENV_PATH}`; - - const { error } = await runCLI(args); - expect(error).toBeNull(); - }); - - describe("Secret environment variables", () => { - jest.setTimeout(10000); - - // Reads secret environment values from system environment - test("Successfully picks the values for secret environment variables from `process.env` and persists the variables set from the pre-request script", async () => { - const env = { - ...process.env, - secretBearerToken: "test-token", - secretBasicAuthUsername: "test-user", - secretBasicAuthPassword: "test-pass", - secretQueryParamValue: "secret-query-param-value", - secretBodyValue: "secret-body-value", - secretHeaderValue: "secret-header-value", - }; - - const COLL_PATH = getTestJsonFilePath( - "secret-envs-coll.json", - "collection" - ); - const ENVS_PATH = getTestJsonFilePath("secret-envs.json", "environment"); - const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; - - const { error, stdout } = await runCLI(args, { env }); - - expect(stdout).toContain( - "https://httpbin.org/basic-auth/*********/*********" - ); - expect(error).toBeNull(); - }); - - // Prefers values specified in the environment export file over values set in the system environment - test("Successfully picks the values for secret environment variables set directly in the environment export file and persists the environment variables set from the pre-request script", async () => { - const COLL_PATH = getTestJsonFilePath( - "secret-envs-coll.json", - "collection" - ); - const ENVS_PATH = getTestJsonFilePath( - "secret-supplied-values-envs.json", - "environment" - ); - const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; - - const { error, stdout } = await runCLI(args); - - expect(stdout).toContain( - "https://httpbin.org/basic-auth/*********/*********" - ); - expect(error).toBeNull(); - }); - - // Values set from the scripting context takes the highest precedence - test("Setting values for secret environment variables from the pre-request script overrides values set at the supplied environment export file", async () => { - const COLL_PATH = getTestJsonFilePath( - "secret-envs-persistence-coll.json", - "collection" - ); - const ENVS_PATH = getTestJsonFilePath( - "secret-supplied-values-envs.json", - "environment" - ); - const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; - - const { error, stdout } = await runCLI(args); - - expect(stdout).toContain( - "https://httpbin.org/basic-auth/*********/*********" - ); - expect(error).toBeNull(); - }); - - test("Persists secret environment variable values set from the pre-request script for consumption in the request and post-request script context", async () => { - const COLL_PATH = getTestJsonFilePath( - "secret-envs-persistence-scripting-coll.json", - "collection" - ); - const ENVS_PATH = getTestJsonFilePath( - "secret-envs-persistence-scripting-envs.json", - "environment" - ); - const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; - - const { error } = await runCLI(args); - expect(error).toBeNull(); - }); - }); -}); - -describe("Test `hopp test --delay ` command:", () => { - const VALID_TEST_ARGS = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`; - - test("Errors with the code `INVALID_ARGUMENT` on not supplying a delay value", async () => { - const args = `${VALID_TEST_ARGS} --delay`; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("INVALID_ARGUMENT"); - }); - - test("Errors with the code `INVALID_ARGUMENT` on supplying an invalid delay value", async () => { - const args = `${VALID_TEST_ARGS} --delay 'NaN'`; - const { stderr } = await runCLI(args); - - const out = getErrorCode(stderr); - expect(out).toBe("INVALID_ARGUMENT"); - }); - - test("Successfully performs delayed request execution for a valid delay value", async () => { - const args = `${VALID_TEST_ARGS} --delay 1`; - const { error } = await runCLI(args); - - expect(error).toBeNull(); - }); - - test("Works with the short `-d` flag", async () => { - const args = `${VALID_TEST_ARGS} -d 1`; - const { error } = await runCLI(args); - - expect(error).toBeNull(); - }); -}); diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/commands/__snapshots__/test.spec.ts.snap b/packages/hoppscotch-cli/src/__tests__/e2e/commands/__snapshots__/test.spec.ts.snap new file mode 100644 index 0000000000..070d36bd28 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/commands/__snapshots__/test.spec.ts.snap @@ -0,0 +1,529 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`hopp test [options] > Test\`hopp test --env --reporter-junit [path] > Generates a JUnit report at the default path 1`] = ` +" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + >" +} (ENV_EXPAND_LOOP)]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +" +`; + +exports[`hopp test [options] > Test\`hopp test --env --reporter-junit [path] > Generates a JUnit report at the specified path 1`] = ` +" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + >" +} (ENV_EXPAND_LOOP)]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +" +`; + +exports[`hopp test [options] > Test\`hopp test --env --reporter-junit [path] > Generates a JUnit report for a collection referring to environment variables 1`] = ` +" + + + + + + + + + + + + + + + +" +`; + +exports[`hopp test [options] > Test\`hopp test --env --reporter-junit [path] > Generates a JUnit report for a collection with authorization/headers set at the collection level 1`] = ` +" + + + + + + + + + + + + + + + + + + + + + + + + + +" +`; diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/commands/test.spec.ts b/packages/hoppscotch-cli/src/__tests__/e2e/commands/test.spec.ts new file mode 100644 index 0000000000..f386046ec6 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/commands/test.spec.ts @@ -0,0 +1,956 @@ +import { ExecException } from "child_process"; +import fs from "fs"; +import path from "path"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; + +import { HoppErrorCode } from "../../../types/errors"; +import { getErrorCode, getTestJsonFilePath, runCLI } from "../../utils"; + +describe("hopp test [options] ", { timeout: 100000 }, () => { + const VALID_TEST_ARGS = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`; + + describe("Test `hopp test ` command:", () => { + describe("Argument parsing", () => { + test("Errors with the code `INVALID_ARGUMENT` for not supplying enough arguments", async () => { + const args = "test"; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ARGUMENT"); + }); + + test("Errors with the code `INVALID_ARGUMENT` for an invalid command", async () => { + const args = "invalid-arg"; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ARGUMENT"); + }); + }); + + describe("Supplied collection export file validations", () => { + test("Errors with the code `FILE_NOT_FOUND` if the supplied collection export file doesn't exist", async () => { + const args = "test notfound.json"; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("FILE_NOT_FOUND"); + }); + + test("Errors with the code UNKNOWN_ERROR if the supplied collection export file content isn't valid JSON", async () => { + const args = `test ${getTestJsonFilePath("malformed-coll.json", "collection")}`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("UNKNOWN_ERROR"); + }); + + test("Errors with the code `MALFORMED_COLLECTION` if the supplied collection export file content is malformed", async () => { + const args = `test ${getTestJsonFilePath("malformed-coll-2.json", "collection")}`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("MALFORMED_COLLECTION"); + }); + + test("Errors with the code `INVALID_FILE_TYPE` if the supplied collection export file doesn't end with the `.json` extension", async () => { + const args = `test ${getTestJsonFilePath("notjson-coll.txt", "collection")}`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_FILE_TYPE"); + }); + + test("Fails if the collection file includes scripts with incorrect API usage and failed assertions", async () => { + const args = `test ${getTestJsonFilePath("fails-coll.json", "collection")}`; + const { error } = await runCLI(args); + + expect(error).not.toBeNull(); + expect(error).toMatchObject({ + code: 1, + }); + }); + }); + + describe("Versioned entities", () => { + describe("Collections & Requests", () => { + const testFixtures = [ + { fileName: "coll-v1-req-v0.json", collVersion: 1, reqVersion: 0 }, + { fileName: "coll-v1-req-v1.json", collVersion: 1, reqVersion: 1 }, + { fileName: "coll-v2-req-v2.json", collVersion: 2, reqVersion: 2 }, + { fileName: "coll-v2-req-v3.json", collVersion: 2, reqVersion: 3 }, + ]; + + testFixtures.forEach(({ collVersion, fileName, reqVersion }) => { + test(`Successfully processes a supplied collection export file where the collection is based on the "v${collVersion}" schema and the request following the "v${reqVersion}" schema`, async () => { + const args = `test ${getTestJsonFilePath(fileName, "collection")}`; + const { error } = await runCLI(args); + + expect(error).toBeNull(); + }); + }); + }); + + describe("Environments", () => { + const testFixtures = [ + { fileName: "env-v0.json", version: 0 }, + { fileName: "env-v1.json", version: 1 }, + ]; + + testFixtures.forEach(({ fileName, version }) => { + test(`Successfully processes the supplied collection and environment export files where the environment is based on the "v${version}" schema`, async () => { + const ENV_PATH = getTestJsonFilePath(fileName, "environment"); + const args = `test ${getTestJsonFilePath("sample-coll.json", "collection")} --env ${ENV_PATH}`; + const { error } = await runCLI(args); + + expect(error).toBeNull(); + }); + }); + }); + }); + + test("Successfully processes a supplied collection export file of the expected format", async () => { + const args = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`; + const { error } = await runCLI(args); + + expect(error).toBeNull(); + }); + + test("Successfully inherits/overrides authorization and headers specified at the root collection at deeply nested collections", async () => { + const args = `test ${getTestJsonFilePath( + "collection-level-auth-headers-coll.json", + "collection" + )}`; + const { error } = await runCLI(args); + + expect(error).toBeNull(); + }); + + test("Successfully inherits/overrides authorization and headers at each level with multiple child collections", async () => { + const args = `test ${getTestJsonFilePath( + "multiple-child-collections-auth-headers-coll.json", + "collection" + )}`; + const { error } = await runCLI(args); + + expect(error).toBeNull(); + }); + + test("Persists environment variables set in the pre-request script for consumption in the test script", async () => { + const args = `test ${getTestJsonFilePath( + "pre-req-script-env-var-persistence-coll.json", + "collection" + )}`; + const { error } = await runCLI(args); + + expect(error).toBeNull(); + }); + + test("The `Content-Type` header takes priority over the value set at the request body", async () => { + const args = `test ${getTestJsonFilePath( + "content-type-header-scenarios.json", + "collection" + )}`; + const { error } = await runCLI(args); + + expect(error).toBeNull(); + }); + + describe("OAuth 2 Authorization type with Authorization Code Grant Type", () => { + test("Successfully translates the authorization information to headers/query params and sends it along with the request", async () => { + const args = `test ${getTestJsonFilePath( + "oauth2-auth-code-coll.json", + "collection" + )}`; + const { error } = await runCLI(args); + + expect(error).toBeNull(); + }); + }); + + describe("multipart/form-data content type", () => { + test("Successfully derives the relevant headers based and sends the form data in the request body", async () => { + const args = `test ${getTestJsonFilePath( + "oauth2-auth-code-coll.json", + "collection" + )}`; + const { error } = await runCLI(args); + + expect(error).toBeNull(); + }); + }); + }); + + test("Ensures tests run in sequence order based on request path", async () => { + // Expected order of collection runs + const expectedOrder = [ + "root-collection-request", + "folder-1/folder-1-request", + "folder-1/folder-11/folder-11-request", + "folder-1/folder-12/folder-12-request", + "folder-1/folder-13/folder-13-request", + "folder-2/folder-2-request", + "folder-2/folder-21/folder-21-request", + "folder-2/folder-22/folder-22-request", + "folder-2/folder-23/folder-23-request", + "folder-3/folder-3-request", + "folder-3/folder-31/folder-31-request", + "folder-3/folder-32/folder-32-request", + "folder-3/folder-33/folder-33-request", + ]; + + const normalizePath = (path: string) => path.replace(/\\/g, "/"); + + const extractRunningOrder = (stdout: string): string[] => + [...stdout.matchAll(/Running:.*?\/(.*?)\r?\n/g)].map( + ([, path]) => normalizePath(path.replace(/\x1b\[\d+m/g, "")) // Remove ANSI codes and normalize paths + ); + + const args = `test ${getTestJsonFilePath( + "multiple-child-collections-auth-headers-coll.json", + "collection" + )}`; + + const { stdout, error } = await runCLI(args); + + // Verify the actual order matches the expected order + expect(extractRunningOrder(stdout)).toStrictEqual(expectedOrder); + + // Ensure no errors occurred + expect(error).toBeNull(); + }); + + describe("Test `hopp test --env ` command:", () => { + describe("Supplied environment export file validations", () => { + describe("Argument parsing", () => { + test("Errors with the code `INVALID_ARGUMENT` if no file is supplied", async () => { + const args = `${VALID_TEST_ARGS} --env`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ARGUMENT"); + }); + }); + + test("Errors with the code `INVALID_FILE_TYPE` if the supplied environment export file doesn't end with the `.json` extension", async () => { + const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath( + "notjson-coll.txt", + "collection" + )}`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_FILE_TYPE"); + }); + + test("Errors with the code `FILE_NOT_FOUND` if the supplied environment export file doesn't exist", async () => { + const args = `${VALID_TEST_ARGS} --env notfound.json`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("FILE_NOT_FOUND"); + }); + + test("Errors with the code `MALFORMED_ENV_FILE` on supplying a malformed environment export file", async () => { + const ENV_PATH = getTestJsonFilePath( + "malformed-envs.json", + "environment" + ); + const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("MALFORMED_ENV_FILE"); + }); + + test("Errors with the code `BULK_ENV_FILE` on supplying an environment export file based on the bulk environment export format", async () => { + const ENV_PATH = getTestJsonFilePath("bulk-envs.json", "environment"); + const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("BULK_ENV_FILE"); + }); + }); + + test("Successfully resolves values from the supplied environment export file", async () => { + const COLL_PATH = getTestJsonFilePath( + "env-flag-tests-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment"); + const args = `test ${COLL_PATH} --env ${ENV_PATH}`; + + const { error } = await runCLI(args); + expect(error).toBeNull(); + }); + + test("Successfully resolves environment variables referenced in the request body", async () => { + const COLL_PATH = getTestJsonFilePath( + "req-body-env-vars-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath( + "req-body-env-vars-envs.json", + "environment" + ); + const args = `test ${COLL_PATH} --env ${ENV_PATH}`; + + const { error } = await runCLI(args); + expect(error).toBeNull(); + }); + + test("Works with short `-e` flag", async () => { + const COLL_PATH = getTestJsonFilePath( + "env-flag-tests-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment"); + const args = `test ${COLL_PATH} -e ${ENV_PATH}`; + + const { error } = await runCLI(args); + expect(error).toBeNull(); + }); + + describe("Secret environment variables", () => { + // Reads secret environment values from system environment + test("Successfully picks the values for secret environment variables from `process.env` and persists the variables set from the pre-request script", async () => { + const env = { + ...process.env, + secretBearerToken: "test-token", + secretBasicAuthUsername: "test-user", + secretBasicAuthPassword: "test-pass", + secretQueryParamValue: "secret-query-param-value", + secretBodyValue: "secret-body-value", + secretHeaderValue: "secret-header-value", + }; + + const COLL_PATH = getTestJsonFilePath( + "secret-envs-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath("secret-envs.json", "environment"); + const args = `test ${COLL_PATH} --env ${ENV_PATH}`; + + const { error, stdout } = await runCLI(args, { env }); + + expect(stdout).toContain( + "https://httpbin.org/basic-auth/*********/*********" + ); + expect(error).toBeNull(); + }); + + // Prefers values specified in the environment export file over values set in the system environment + test("Successfully picks the values for secret environment variables set directly in the environment export file and persists the environment variables set from the pre-request script", async () => { + const COLL_PATH = getTestJsonFilePath( + "secret-envs-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath( + "secret-supplied-values-envs.json", + "environment" + ); + const args = `test ${COLL_PATH} --env ${ENV_PATH}`; + + const { error, stdout } = await runCLI(args); + + expect(stdout).toContain( + "https://httpbin.org/basic-auth/*********/*********" + ); + expect(error).toBeNull(); + }); + + // Values set from the scripting context takes the highest precedence + test("Setting values for secret environment variables from the pre-request script overrides values set at the supplied environment export file", async () => { + const COLL_PATH = getTestJsonFilePath( + "secret-envs-persistence-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath( + "secret-supplied-values-envs.json", + "environment" + ); + const args = `test ${COLL_PATH} --env ${ENV_PATH}`; + + const { error, stdout } = await runCLI(args); + + expect(stdout).toContain( + "https://httpbin.org/basic-auth/*********/*********" + ); + expect(error).toBeNull(); + }); + + test("Persists secret environment variable values set from the pre-request script for consumption in the request and post-request script context", async () => { + const COLL_PATH = getTestJsonFilePath( + "secret-envs-persistence-scripting-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath( + "secret-envs-persistence-scripting-envs.json", + "environment" + ); + + const args = `test ${COLL_PATH} --env ${ENV_PATH}`; + + const { error } = await runCLI(args); + expect(error).toBeNull(); + }); + }); + + describe("Request variables", () => { + test("Picks active request variables and ignores inactive entries alongside the usage of environment variables", async () => { + const env = { + ...process.env, + secretBasicAuthPasswordEnvVar: "password", + }; + + const COLL_PATH = getTestJsonFilePath( + "request-vars-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath( + "request-vars-envs.json", + "environment" + ); + + const args = `test ${COLL_PATH} --env ${ENV_PATH}`; + + const { error, stdout } = await runCLI(args, { env }); + expect(stdout).toContain( + "https://echo.hoppscotch.io/********/********" + ); + expect(error).toBeNull(); + }); + }); + + describe("AWS Signature Authorization type", () => { + test("Successfully translates the authorization information to headers/query params and sends it along with the request", async () => { + const env = { + ...process.env, + secretKey: "test-secret-key", + serviceToken: "test-token", + }; + + const COLL_PATH = getTestJsonFilePath( + "aws-signature-auth-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath( + "aws-signature-auth-envs.json", + "environment" + ); + + const args = `test ${COLL_PATH} -e ${ENV_PATH}`; + const { error } = await runCLI(args, { env }); + + expect(error).toBeNull(); + }); + }); + + describe("Digest Authorization type", () => { + test("Successfully translates the authorization information to headers/query params and sends it along with the request", async () => { + const COLL_PATH = getTestJsonFilePath( + "digest-auth-success-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath( + "digest-auth-envs.json", + "environment" + ); + + const args = `test ${COLL_PATH} -e ${ENV_PATH}`; + const { error } = await runCLI(args); + expect(error).toBeNull(); + }); + }); + + test("Supports disabling request retries", async () => { + const COLL_PATH = getTestJsonFilePath( + "digest-auth-failure-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath( + "digest-auth-envs.json", + "environment" + ); + + const args = `test ${COLL_PATH} -e ${ENV_PATH}`; + const { error } = await runCLI(args); + + expect(error).toBeTruthy(); + }); + }); + + describe("Test `hopp test --delay ` command:", () => { + describe("Argument parsing", () => { + test("Errors with the code `INVALID_ARGUMENT` on not supplying a delay value", async () => { + const args = `${VALID_TEST_ARGS} --delay`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ARGUMENT"); + }); + + test("Errors with the code `INVALID_ARGUMENT` on supplying an invalid delay value", async () => { + const args = `${VALID_TEST_ARGS} --delay 'NaN'`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ARGUMENT"); + }); + }); + + test("Successfully performs delayed request execution for a valid delay value", async () => { + const args = `${VALID_TEST_ARGS} --delay 1`; + const { error } = await runCLI(args); + + expect(error).toBeNull(); + }); + + test("Works with the short `-d` flag", async () => { + const args = `${VALID_TEST_ARGS} -d 1`; + const { error } = await runCLI(args); + + expect(error).toBeNull(); + }); + }); + + // Future TODO: Enable once a proper e2e test environment is set up locally + describe.skip("Test `hopp test --env --token --server ` command:", () => { + const { + REQ_BODY_ENV_VARS_COLL_ID, + COLLECTION_LEVEL_HEADERS_AUTH_COLL_ID, + REQ_BODY_ENV_VARS_ENVS_ID, + PERSONAL_ACCESS_TOKEN, + } = process.env; + + if ( + !REQ_BODY_ENV_VARS_COLL_ID || + !COLLECTION_LEVEL_HEADERS_AUTH_COLL_ID || + !REQ_BODY_ENV_VARS_ENVS_ID || + !PERSONAL_ACCESS_TOKEN + ) { + return; + } + + const SERVER_URL = "https://stage-shc.hoppscotch.io/backend"; + + describe("Argument parsing", () => { + test("Errors with the code `INVALID_ARGUMENT` on not supplying a value for the `--token` flag", async () => { + const args = `test ${REQ_BODY_ENV_VARS_COLL_ID} --token`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ARGUMENT"); + }); + + test("Errors with the code `INVALID_ARGUMENT` on not supplying a value for the `--server` flag", async () => { + const args = `test ${REQ_BODY_ENV_VARS_COLL_ID} --server`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ARGUMENT"); + }); + }); + + describe("Workspace access validations", () => { + const INVALID_COLLECTION_ID = "invalid-coll-id"; + const INVALID_ENVIRONMENT_ID = "invalid-env-id"; + const INVALID_ACCESS_TOKEN = "invalid-token"; + + test("Errors with the code `TOKEN_INVALID` if the supplied access token is invalid", async () => { + const args = `test ${REQ_BODY_ENV_VARS_COLL_ID} --token ${INVALID_ACCESS_TOKEN} --server ${SERVER_URL}`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("TOKEN_INVALID"); + }); + + test("Errors with the code `INVALID_ID` if the supplied collection ID is invalid", async () => { + const args = `test ${INVALID_COLLECTION_ID} --token ${PERSONAL_ACCESS_TOKEN} --server ${SERVER_URL}`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ID"); + }); + + test("Errors with the code `INVALID_ID` if the supplied environment ID is invalid", async () => { + const args = `test ${REQ_BODY_ENV_VARS_COLL_ID} --env ${INVALID_ENVIRONMENT_ID} --token ${PERSONAL_ACCESS_TOKEN} --server ${SERVER_URL}`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ID"); + }); + + test("Errors with the code `INVALID_SERVER_URL` if not supplying a valid SH instance server URL", async () => { + // FE URL of the staging SHC instance + const INVALID_SERVER_URL = "https://stage-shc.hoppscotch.io"; + const args = `test ${REQ_BODY_ENV_VARS_COLL_ID} --env ${REQ_BODY_ENV_VARS_ENVS_ID} --token ${PERSONAL_ACCESS_TOKEN} --server ${INVALID_SERVER_URL}`; + + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_SERVER_URL"); + }); + + test("Errors with the code `SERVER_CONNECTION_REFUSED` if supplying an SH instance server URL that doesn't follow URL semantics", async () => { + const INVALID_URL = "invalid-url"; + const args = `test ${REQ_BODY_ENV_VARS_COLL_ID} --env ${REQ_BODY_ENV_VARS_ENVS_ID} --token ${PERSONAL_ACCESS_TOKEN} --server ${INVALID_URL}`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("SERVER_CONNECTION_REFUSED"); + }); + }); + + test("Successfully retrieves a collection with the ID", async () => { + const args = `test ${COLLECTION_LEVEL_HEADERS_AUTH_COLL_ID} --token ${PERSONAL_ACCESS_TOKEN} --server ${SERVER_URL}`; + + const { error } = await runCLI(args); + expect(error).toBeNull(); + }); + + test("Successfully retrieves collections and environments from a workspace using their respective IDs", async () => { + const args = `test ${REQ_BODY_ENV_VARS_COLL_ID} --env ${REQ_BODY_ENV_VARS_ENVS_ID} --token ${PERSONAL_ACCESS_TOKEN} --server ${SERVER_URL}`; + + const { error } = await runCLI(args); + expect(error).toBeNull(); + }); + + test("Supports specifying collection file path along with environment ID", async () => { + const COLL_PATH = getTestJsonFilePath( + "req-body-env-vars-coll.json", + "collection" + ); + const args = `test ${COLL_PATH} --env ${REQ_BODY_ENV_VARS_ENVS_ID} --token ${PERSONAL_ACCESS_TOKEN} --server ${SERVER_URL}`; + + const { error } = await runCLI(args); + expect(error).toBeNull(); + }); + + test("Supports specifying environment file path along with collection ID", async () => { + const ENV_PATH = getTestJsonFilePath( + "req-body-env-vars-envs.json", + "environment" + ); + const args = `test ${REQ_BODY_ENV_VARS_COLL_ID} --env ${ENV_PATH} --token ${PERSONAL_ACCESS_TOKEN} --server ${SERVER_URL}`; + + const { error } = await runCLI(args); + expect(error).toBeNull(); + }); + + test("Supports specifying both collection and environment file paths", async () => { + const COLL_PATH = getTestJsonFilePath( + "req-body-env-vars-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath( + "req-body-env-vars-envs.json", + "environment" + ); + const args = `test ${COLL_PATH} --env ${ENV_PATH} --token ${PERSONAL_ACCESS_TOKEN}`; + + const { error } = await runCLI(args); + expect(error).toBeNull(); + }); + }); + + describe("Test`hopp test --env --reporter-junit [path]", () => { + const genPath = path.resolve("hopp-cli-test"); + + // Helper function to replace dynamic values before generating test snapshots + // Currently scoped to JUnit report generation + const replaceDynamicValuesInStr = (input: string): string => + input.replace( + /(time|timestamp)="[^"]+"/g, + (_, attr) => `${attr}="${attr}"` + ); + + beforeAll(() => { + fs.mkdirSync(genPath); + }); + + afterAll(() => { + fs.rmdirSync(genPath, { recursive: true }); + }); + + test("Report export fails with the code `REPORT_EXPORT_FAILED` while encountering an error during path creation", async () => { + const exportPath = "hopp-junit-report.xml"; + + const COLL_PATH = getTestJsonFilePath("passes-coll.json", "collection"); + + const invalidPath = + process.platform === "win32" + ? "Z:/non-existent-path/report.xml" + : "/non-existent/report.xml"; + + const args = `test ${COLL_PATH} --reporter-junit ${invalidPath}`; + + const { stdout, stderr } = await runCLI(args, { + cwd: path.resolve("hopp-cli-test"), + }); + + const out = getErrorCode(stderr); + expect(out).toBe("REPORT_EXPORT_FAILED"); + + expect(stdout).not.toContain( + `Successfully exported the JUnit report to: ${exportPath}` + ); + }); + + test("Generates a JUnit report at the default path", async () => { + const exportPath = "hopp-junit-report.xml"; + + const COLL_PATH = getTestJsonFilePath( + "test-junit-report-export-coll.json", + "collection" + ); + + const args = `test ${COLL_PATH} --reporter-junit`; + + const { stdout } = await runCLI(args, { + cwd: path.resolve("hopp-cli-test"), + }); + + expect(stdout).not.toContain( + `Overwriting the pre-existing path: ${exportPath}` + ); + + expect(stdout).toContain( + `Successfully exported the JUnit report to: ${exportPath}` + ); + + const fileContents = fs + .readFileSync(path.resolve(genPath, exportPath)) + .toString(); + + expect(replaceDynamicValuesInStr(fileContents)).toMatchSnapshot(); + }); + + test("Generates a JUnit report at the specified path", async () => { + const exportPath = "outer-dir/inner-dir/report.xml"; + + const COLL_PATH = getTestJsonFilePath( + "test-junit-report-export-coll.json", + "collection" + ); + + const args = `test ${COLL_PATH} --reporter-junit ${exportPath}`; + + const { stdout } = await runCLI(args, { + cwd: path.resolve("hopp-cli-test"), + }); + + expect(stdout).not.toContain( + `Overwriting the pre-existing path: ${exportPath}` + ); + + expect(stdout).toContain( + `Successfully exported the JUnit report to: ${exportPath}` + ); + + const fileContents = fs + .readFileSync(path.resolve(genPath, exportPath)) + .toString(); + + expect(replaceDynamicValuesInStr(fileContents)).toMatchSnapshot(); + }); + + test("Generates a JUnit report for a collection with authorization/headers set at the collection level", async () => { + const exportPath = "hopp-junit-report.xml"; + + const COLL_PATH = getTestJsonFilePath( + "collection-level-auth-headers-coll.json", + "collection" + ); + + const args = `test ${COLL_PATH} --reporter-junit`; + + const { stdout } = await runCLI(args, { + cwd: path.resolve("hopp-cli-test"), + }); + + expect(stdout).toContain( + `Overwriting the pre-existing path: ${exportPath}` + ); + + expect(stdout).toContain( + `Successfully exported the JUnit report to: ${exportPath}` + ); + + const fileContents = fs + .readFileSync(path.resolve(genPath, exportPath)) + .toString(); + + expect(replaceDynamicValuesInStr(fileContents)).toMatchSnapshot(); + }); + + test("Generates a JUnit report for a collection referring to environment variables", async () => { + const exportPath = "hopp-junit-report.xml"; + + const COLL_PATH = getTestJsonFilePath( + "req-body-env-vars-coll.json", + "collection" + ); + const ENV_PATH = getTestJsonFilePath( + "req-body-env-vars-envs.json", + "environment" + ); + + const args = `test ${COLL_PATH} --env ${ENV_PATH} --reporter-junit`; + + const { stdout } = await runCLI(args, { + cwd: path.resolve("hopp-cli-test"), + }); + + expect(stdout).toContain( + `Overwriting the pre-existing path: ${exportPath}` + ); + + expect(stdout).toContain( + `Successfully exported the JUnit report to: ${exportPath}` + ); + + const fileContents = fs + .readFileSync(path.resolve(genPath, exportPath)) + .toString(); + + expect(replaceDynamicValuesInStr(fileContents)).toMatchSnapshot(); + }); + }); + + describe("Test `hopp test --iteration-count ` command:", () => { + const VALID_TEST_ARGS = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`; + + test("Errors with the code `INVALID_ARGUMENT` on not supplying an iteration count", async () => { + const args = `${VALID_TEST_ARGS} --iteration-count`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ARGUMENT"); + }); + + test("Errors with the code `INVALID_ARGUMENT` on supplying an invalid iteration count", async () => { + const args = `${VALID_TEST_ARGS} --iteration-count NaN`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ARGUMENT"); + }); + + test("Errors with the code `INVALID_ARGUMENT` on supplying an iteration count below `1`", async () => { + const args = `${VALID_TEST_ARGS} --iteration-count -5`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ARGUMENT"); + }); + + test("Successfully executes all requests in the collection iteratively based on the specified iteration count", async () => { + const iterationCount = 3; + const args = `${VALID_TEST_ARGS} --iteration-count ${iterationCount}`; + const { error, stdout } = await runCLI(args); + + // Logs iteration count in each pass + Array.from({ length: 3 }).forEach((_, idx) => + expect(stdout).include(`Iteration: ${idx + 1}/${iterationCount}`) + ); + expect(error).toBeNull(); + }); + + test("Doesn't log iteration count if the value supplied is `1`", async () => { + const args = `${VALID_TEST_ARGS} --iteration-count 1`; + const { error, stdout } = await runCLI(args); + + expect(stdout).not.include(`Iteration: 1/1`); + + expect(error).toBeNull(); + }); + }); + + describe("Test `hopp test --iteration-data ` command:", () => { + describe("Supplied data export file validations", () => { + const VALID_TEST_ARGS = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`; + + test("Errors with the code `INVALID_ARGUMENT` if no file is supplied", async () => { + const args = `${VALID_TEST_ARGS} --iteration-data`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_ARGUMENT"); + }); + + test("Errors with the code `INVALID_DATA_FILE_TYPE` if the supplied data file doesn't end with the `.csv` extension", async () => { + const args = `${VALID_TEST_ARGS} --iteration-data ${getTestJsonFilePath( + "notjson-coll.txt", + "collection" + )}`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("INVALID_DATA_FILE_TYPE"); + }); + + test("Errors with the code `FILE_NOT_FOUND` if the supplied data export file doesn't exist", async () => { + const args = `${VALID_TEST_ARGS} --iteration-data notfound.csv`; + const { stderr } = await runCLI(args); + + const out = getErrorCode(stderr); + expect(out).toBe("FILE_NOT_FOUND"); + }); + }); + + test("Prioritizes values from the supplied data export file over environment variables with relevant fallbacks for missing entries", async () => { + const COLL_PATH = getTestJsonFilePath( + "iteration-data-tests-coll.json", + "collection" + ); + const ITERATION_DATA_PATH = getTestJsonFilePath( + "iteration-data-export.csv", + "environment" + ); + const ENV_PATH = getTestJsonFilePath( + "iteration-data-envs.json", + "environment" + ); + const args = `test ${COLL_PATH} --iteration-data ${ITERATION_DATA_PATH} -e ${ENV_PATH}`; + + const { error, stdout } = await runCLI(args); + + const iterationCount = 3; + + // Even though iteration count is not supplied, it will be inferred from the iteration data size + Array.from({ length: iterationCount }).forEach((_, idx) => + expect(stdout).include(`Iteration: ${idx + 1}/${iterationCount}`) + ); + + expect(error).toBeNull(); + }); + + test("Iteration count takes priority if supplied instead of inferring from the iteration data size", async () => { + const COLL_PATH = getTestJsonFilePath( + "iteration-data-tests-coll.json", + "collection" + ); + const ITERATION_DATA_PATH = getTestJsonFilePath( + "iteration-data-export.csv", + "environment" + ); + const ENV_PATH = getTestJsonFilePath( + "iteration-data-envs.json", + "environment" + ); + + const iterationCount = 5; + const args = `test ${COLL_PATH} --iteration-data ${ITERATION_DATA_PATH} -e ${ENV_PATH} --iteration-count ${iterationCount}`; + + const { error, stdout } = await runCLI(args); + + Array.from({ length: iterationCount }).forEach((_, idx) => + expect(stdout).include(`Iteration: ${idx + 1}/${iterationCount}`) + ); + + expect(error).toBeNull(); + }); + }); +}); diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/aws-signature-auth-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/aws-signature-auth-coll.json new file mode 100644 index 0000000000..eae88b752c --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/aws-signature-auth-coll.json @@ -0,0 +1,101 @@ +{ + "v": 3, + "name": "AWS Signature Auth - collection", + "folders": [], + "requests": [ + { + "v": "7", + "id": "cm0dm70cw000687bnxi830zz7", + "auth": { + "addTo": "HEADERS", + "region": "<>", + "authType": "aws-signature", + "accessKey": "<>", + "secretKey": "<>", + "authActive": true, + "serviceName": "<>", + "serviceToken": "", + "grantTypeInfo": { + "token": "", + "isPKCE": false, + "clientID": "", + "grantType": "AUTHORIZATION_CODE", + "authEndpoint": "", + "clientSecret": "", + "tokenEndpoint": "", + "codeVerifierMethod": "S256" + } + }, + "body": { + "body": null, + "contentType": null + }, + "name": "aws-signature-auth-headers", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "<>", + "testScript": "pw.test(\"Successfully sends relevant AWS signature information via headers\", ()=> {\n const { headers } = pw.response.body\n\n // Dynamic values, hence comparing the type.\n pw.expect(headers[\"authorization\"]).toBeType(\"string\");\n pw.expect(headers[\"x-amz-date\"]).toBeType(\"string\");\n \n pw.expect(headers[\"x-amz-content-sha256\"]).toBe(\"UNSIGNED-PAYLOAD\")\n \n // No session token supplied\n pw.expect(headers[\"x-amz-security-token\"]).toBe(undefined)\n \n});", + "preRequestScript": "", + "requestVariables": [ + { + "key": "secretVarKey", + "value": "<>", + "active": true + } + ] + }, + { + "v": "7", + "id": "cm0dm70cw000687bnxi830zz7", + "auth": { + "addTo": "QUERY_PARAMS", + "region": "<>", + "authType": "aws-signature", + "accessKey": "<>", + "secretKey": "<>", + "authActive": true, + "serviceName": "<>", + "serviceToken": "<>", + "grantTypeInfo": { + "token": "", + "isPKCE": false, + "clientID": "", + "grantType": "AUTHORIZATION_CODE", + "authEndpoint": "", + "clientSecret": "", + "tokenEndpoint": "", + "codeVerifierMethod": "S256" + } + }, + "body": { + "body": null, + "contentType": null + }, + "name": "aws-signature-auth-query-params", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "<>", + "testScript": "pw.test(\"Successfully sends relevant AWS signature information via query params\", ()=> {\n const { args } = pw.response.body\n pw.expect(args[\"X-Amz-Algorithm\"]).toBe(\"AWS4-HMAC-SHA256\");\n pw.expect(args[\"X-Amz-Algorithm\"]).toBe(\"AWS4-HMAC-SHA256\");\n pw.expect(args[\"X-Amz-Credential\"]).toInclude(\"test-access-key\");\n pw.expect(args[\"X-Amz-Credential\"]).toInclude(\"eu-west-1/s3\");\n\n // Dynamic values, hence comparing the type.\n pw.expect(args[\"X-Amz-Date\"]).toBeType(\"string\");\n pw.expect(args[\"X-Amz-Signature\"]).toBeType(\"string\");\n\n pw.expect(args[\"X-Amz-Expires\"]).toBe(\"86400\")\n pw.expect(args[\"X-Amz-SignedHeaders\"]).toBe(\"host\")\n pw.expect(args[\"X-Amz-Security-Token\"]).toBe(\"test-token\")\n \n});", + "preRequestScript": "", + "requestVariables": [ + { + "key": "awsRegion", + "value": "eu-west-1", + "active": true + }, + { + "key": "secretKey", + "value": "test-secret-key-overriden", + "active": true + } + ] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] +} diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v1-req-v0.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v1-req-v0.json new file mode 100644 index 0000000000..bf63d2ae1b --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v1-req-v0.json @@ -0,0 +1,55 @@ +{ + "v": 1, + "name": "coll-v1", + "folders": [ + { + "v": 1, + "name": "coll-v1-child", + "folders": [], + "requests": [ + { + "url": "https://echo.hoppscotch.io", + "path": "/get", + "headers": [ + { "key": "Inactive-Header", "value": "Inactive Header", "active": false }, + { "key": "Authorization", "value": "Bearer token123", "active": true } + ], + "params": [ + { "key": "key", "value": "value", "active": true }, + { "key": "inactive-key", "value": "inactive-param", "active": false } + ], + "name": "req-v0-II", + "method": "GET", + "preRequestScript": "", + "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})", + "contentType": "application/json", + "body": "", + "auth": "Bearer Token", + "bearerToken": "token123" + } + ] + } + ], + "requests": [ + { + "url": "https://echo.hoppscotch.io", + "path": "/get", + "headers": [ + { "key": "Inactive-Header", "value": "Inactive Header", "active": false }, + { "key": "Authorization", "value": "Bearer token123", "active": true } + ], + "params": [ + { "key": "key", "value": "value", "active": true }, + { "key": "inactive-key", "value": "inactive-param", "active": false } + ], + "name": "req-v0", + "method": "GET", + "preRequestScript": "", + "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})", + "contentType": "application/json", + "body": "", + "auth": "Bearer Token", + "bearerToken": "token123" + } + ] +} diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v1-req-v1.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v1-req-v1.json new file mode 100644 index 0000000000..9bfc979e8f --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v1-req-v1.json @@ -0,0 +1,97 @@ +{ + "v": 1, + "name": "coll-v1", + "folders": [ + { + "v": 1, + "name": "coll-v1-child", + "folders": [], + "requests": [ + { + "v": "1", + "endpoint": "https://echo.hoppscotch.io", + "headers": [ + { + "key": "Inactive-Header", + "value": "Inactive Header", + "active": false + }, + { + "key": "Authorization", + "value": "Bearer token123", + "active": true + } + ], + "params": [ + { + "key": "key", + "value": "value", + "active": true + }, + { + "key": "inactive-key", + "value": "inactive-param", + "active": false + } + ], + "name": "req-v1-II", + "method": "GET", + "preRequestScript": "", + "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})", + "body": { + "contentType": null, + "body": null + }, + "auth": { + "authType": "bearer", + "authActive": true, + "token": "token123" + } + } + ] + } + ], + "requests": [ + { + "v": "1", + "endpoint": "https://echo.hoppscotch.io", + "headers": [ + { + "key": "Inactive-Header", + "value": "Inactive Header", + "active": false + }, + { + "key": "Authorization", + "value": "Bearer token123", + "active": true + } + ], + "params": [ + { + "key": "key", + "value": "value", + "active": true + }, + { + "key": "inactive-key", + "value": "inactive-param", + "active": false + } + ], + "name": "req-v1", + "method": "GET", + "preRequestScript": "", + "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})", + "body": { + "contentType": null, + "body": null + }, + "auth": { + "authType": "bearer", + "authActive": true, + "token": "token123" + } + } + ] +} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v2-req-v2.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v2-req-v2.json new file mode 100644 index 0000000000..0bcbd13217 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v2-req-v2.json @@ -0,0 +1,109 @@ +{ + "v": 2, + "name": "coll-v2", + "folders": [ + { + "v": 2, + "name": "coll-v2-child", + "folders": [], + "requests": [ + { + "v": "2", + "endpoint": "https://echo.hoppscotch.io", + "headers": [ + { + "key": "Inactive-Header", + "value": "Inactive Header", + "active": false + }, + { + "key": "Authorization", + "value": "Bearer token123", + "active": true + } + ], + "params": [ + { + "key": "key", + "value": "value", + "active": true + }, + { + "key": "inactive-key", + "value": "inactive-param", + "active": false + } + ], + "name": "req-v2-II", + "method": "GET", + "preRequestScript": "", + "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})", + "body": { + "contentType": null, + "body": null + }, + "auth": { + "authType": "bearer", + "authActive": true, + "token": "token123" + }, + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] + } + ], + "requests": [ + { + "v": "2", + "endpoint": "https://echo.hoppscotch.io", + "headers": [ + { + "key": "Inactive-Header", + "value": "Inactive Header", + "active": false + }, + { + "key": "Authorization", + "value": "Bearer token123", + "active": true + } + ], + "params": [ + { + "key": "key", + "value": "value", + "active": true + }, + { + "key": "inactive-key", + "value": "inactive-param", + "active": false + } + ], + "name": "req-v2", + "method": "GET", + "preRequestScript": "", + "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})", + "body": { + "contentType": null, + "body": null + }, + "auth": { + "authType": "bearer", + "authActive": true, + "token": "token123" + }, + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] +} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v2-req-v3.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v2-req-v3.json new file mode 100644 index 0000000000..916e809db0 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/coll-v2-req-v3.json @@ -0,0 +1,109 @@ +{ + "v": 2, + "name": "coll-v2", + "folders": [ + { + "v": 2, + "name": "coll-v2-child", + "folders": [], + "requests": [ + { + "v": "3", + "endpoint": "https://echo.hoppscotch.io", + "headers": [ + { + "key": "Inactive-Header", + "value": "Inactive Header", + "active": false + }, + { + "key": "Authorization", + "value": "Bearer token123", + "active": true + } + ], + "params": [ + { + "key": "key", + "value": "value", + "active": true + }, + { + "key": "inactive-key", + "value": "inactive-param", + "active": false + } + ], + "name": "req-v3-II", + "method": "GET", + "preRequestScript": "", + "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})", + "body": { + "contentType": null, + "body": null + }, + "auth": { + "authType": "bearer", + "authActive": true, + "token": "token123" + }, + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] + } + ], + "requests": [ + { + "v": "3", + "endpoint": "https://echo.hoppscotch.io", + "headers": [ + { + "key": "Inactive-Header", + "value": "Inactive Header", + "active": false + }, + { + "key": "Authorization", + "value": "Bearer token123", + "active": true + } + ], + "params": [ + { + "key": "key", + "value": "value", + "active": true + }, + { + "key": "inactive-key", + "value": "inactive-param", + "active": false + } + ], + "name": "req-v3", + "method": "GET", + "preRequestScript": "", + "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})", + "body": { + "contentType": null, + "body": null + }, + "auth": { + "authType": "bearer", + "authActive": true, + "token": "token123" + }, + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] +} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/collection-level-headers-auth-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/collection-level-auth-headers-coll.json similarity index 99% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/collection-level-headers-auth-coll.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/collection-level-auth-headers-coll.json index ca986c8ae7..bdca78c61d 100644 --- a/packages/hoppscotch-cli/src/__tests__/samples/collections/collection-level-headers-auth-coll.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/collection-level-auth-headers-coll.json @@ -75,7 +75,7 @@ "auth": { "authType": "api-key", "authActive": true, - "addTo": "Headers", + "addTo": "HEADERS", "key": "key", "value": "test-key" }, diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/content-type-header-scenarios.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/content-type-header-scenarios.json new file mode 100644 index 0000000000..9aea2c0f25 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/content-type-header-scenarios.json @@ -0,0 +1,171 @@ +{ + "v": 2, + "name": "content-type-header-scenarios", + "folders": [], + "requests": [ + { + "v": "6", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": "\n\n \n 12345\n John Doe\n john.doe@example.com\n \n \n 98765\n Sample Product\n 2\n \n\n", + "contentType": "text/xml" + }, + "name": "content-type-header-assignment", + "method": "POST", + "params": [], + "headers": [], + "endpoint": "https://echo.hoppscotch.io", + "testScript": "pw.test(\"The `Content-Type` header is assigned the content type value set at the request body level\", ()=> {\n pw.expect(pw.response.body.headers[\"content-type\"]).toBe(\"text/xml\");\n});", + "preRequestScript": "", + "requestVariables": [] + }, + { + "v": "6", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": "\n\n \n 12345\n John Doe\n john.doe@example.com\n \n \n 98765\n Sample Product\n 2\n \n\n", + "contentType": "application/json" + }, + "name": "content-type-header-override", + "method": "POST", + "params": [], + "headers": [ + { + "key": "Content-Type", + "value": "application/xml", + "active": true + } + ], + "endpoint": "https://echo.hoppscotch.io", + "testScript": "pw.test(\"The `Content-Type` header overrides the content type value set at the request body level\", ()=> {\n pw.expect(pw.response.body.headers[\"content-type\"]).toBe(\"application/xml\");\n});", + "preRequestScript": "", + "requestVariables": [] + }, + { + "v": "6", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": "\n\n \n 12345\n John Doe\n john.doe@example.com\n \n \n 98765\n Sample Product\n 2\n \n\n", + "contentType": "application/json" + }, + "name": "multiple-content-type-headers", + "method": "POST", + "params": [], + "headers": [ + { + "key": "Content-Type", + "value": "text/xml", + "active": true + }, + { + "key": "Content-Type", + "value": "application/json", + "active": true + }, + { + "key": "Content-Type", + "value": "application/xml", + "active": true + } + ], + "endpoint": "https://echo.hoppscotch.io", + "testScript": "pw.test(\"The last occurrence will be considered among multiple `Content-Type` headers\", ()=> {\n pw.expect(pw.response.body.headers[\"content-type\"]).toBe(\"application/xml\");\n});", + "preRequestScript": "", + "requestVariables": [] + }, + { + "v": "6", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": "\n\n \n 12345\n John Doe\n john.doe@example.com\n \n \n 98765\n Sample Product\n 2\n \n\n", + "contentType": null + }, + "name": "multiple-content-type-headers-different-casing", + "method": "POST", + "params": [], + "headers": [ + { + "key": "Content-Type", + "value": "text/xml", + "active": true + }, + { + "key": "content-Type", + "value": "application/json", + "active": true + }, + { + "key": "Content-type", + "value": "text/plain", + "active": true + }, + { + "key": "CONTENT-TYPE", + "value": "application/xml", + "active": true + } + ], + "endpoint": "https://echo.hoppscotch.io", + "testScript": "pw.test(\"The last occurrence will be considered among multiple `Content-Type` headers following different casing\", ()=> {\n pw.expect(pw.response.body.headers[\"content-type\"]).toBe(\"application/xml\");\n});", + "preRequestScript": "", + "requestVariables": [] + }, + { + "v": "6", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": "\n\n \n 12345\n John Doe\n john.doe@example.com\n \n \n 98765\n Sample Product\n 2\n \n\n", + "contentType": null + }, + "name": "multiple-content-type-headers-different-casing-without-value-set-at-body", + "method": "POST", + "params": [], + "headers": [ + { + "key": "Content-Type", + "value": "text/xml", + "active": true + }, + { + "key": "content-Type", + "value": "application/json", + "active": true + }, + { + "key": "Content-type", + "value": "text/plain", + "active": true + }, + { + "key": "CONTENT-TYPE", + "value": "application/xml", + "active": true + } + ], + "endpoint": "https://echo.hoppscotch.io", + "testScript": "pw.test(\"The content type is inferred from the `Content-Type` header if not set at the request body\", ()=> {\n pw.expect(pw.response.body.headers[\"content-type\"]).toBe(\"application/xml\");\n});", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] +} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/digest-auth-failure-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/digest-auth-failure-coll.json new file mode 100644 index 0000000000..d8ab5ef259 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/digest-auth-failure-coll.json @@ -0,0 +1,43 @@ +{ + "v": 3, + "name": "Digest Auth (failure state) - collection", + "folders": [], + "requests": [ + { + "v": "8", + "id": "cm0dm70cw000687bnxi830zz7", + "auth": { + "authType": "digest", + "authActive": true, + "username": "<>", + "password": "<>", + "realm": "", + "nonce": "", + "algorithm": "MD5", + "qop": "auth", + "nc": "", + "cnonce": "", + "opaque": "", + "disableRetry": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "digest-auth-headers", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "<>", + "testScript": "pw.test(\"Status code is not 200\", ()=> { pw.expect(pw.response.status).not.toBe(200);}); \n pw.test(\"Receives the www-authenticate header\", ()=> { pw.expect(pw.response.headers['www-authenticate']).not.toBeType('string');});", + "preRequestScript": "", + "responses": {}, + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] +} diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/digest-auth-success-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/digest-auth-success-coll.json new file mode 100644 index 0000000000..16f0b6fca4 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/digest-auth-success-coll.json @@ -0,0 +1,43 @@ +{ + "v": 3, + "name": "Digest Auth (success state) - collection", + "folders": [], + "requests": [ + { + "v": "8", + "id": "cm0dm70cw000687bnxi830zz7", + "auth": { + "authType": "digest", + "authActive": true, + "username": "<>", + "password": "<>", + "realm": "", + "nonce": "", + "algorithm": "MD5", + "qop": "auth", + "nc": "", + "cnonce": "", + "opaque": "", + "disableRetry": false + }, + "body": { + "body": null, + "contentType": null + }, + "name": "digest-auth-headers", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "<>", + "testScript": "pw.test(\"Status code is 200\", ()=> { pw.expect(pw.response.status).toBe(200);}); \n pw.test(\"Receives the www-authenticate header\", ()=> { pw.expect(pw.response.headers['www-authenticate']).toBeType('string');});", + "preRequestScript": "", + "responses": {}, + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] +} diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/env-flag-tests-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/env-flag-tests-coll.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/env-flag-tests-coll.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/env-flag-tests-coll.json diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/fails-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/fails-coll.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/fails-coll.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/fails-coll.json diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/iteration-data-tests-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/iteration-data-tests-coll.json new file mode 100644 index 0000000000..d70aa84532 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/iteration-data-tests-coll.json @@ -0,0 +1,23 @@ +{ + "v": 1, + "name": "iteration-data-tests-coll", + "folders": [], + "requests": [ + { + "v": "3", + "endpoint": "<>", + "name": "test1", + "params": [], + "headers": [], + "method": "POST", + "auth": { "authType": "none", "authActive": true }, + "preRequestScript": "", + "testScript": "// Iteration data is prioritised over environment variables \n const { data, headers } = pw.response.body;\n pw.expect(headers['host']).toBe('echo.hoppscotch.io')\n // Falls back to environment variables for missing entries in data export\n pw.expect(data).toInclude('overriden-body-key-at-environment')\n pw.expect(data).toInclude('body_value')", + "body": { + "contentType": "application/json", + "body": "{\n \"<>\":\"<>\"\n}" + }, + "requestVariables": [] + } + ] +} diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/malformed-coll-2.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/malformed-coll-2.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/malformed-coll-2.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/malformed-coll-2.json diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/malformed-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/malformed-coll.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/malformed-coll.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/malformed-coll.json diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/multipart-form-data-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/multipart-form-data-coll.json new file mode 100644 index 0000000000..9853183bc7 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/multipart-form-data-coll.json @@ -0,0 +1,55 @@ +{ + "v": 3, + "name": "Multpart form data content type - Collection", + "folders": [], + "requests": [ + { + "v": "7", + "endpoint": "https://echo.hoppscotch.io", + "name": "multipart-form-data-sample-req", + "params": [], + "headers": [], + "method": "POST", + "auth": { + "authType": "none", + "authActive": true, + "addTo": "HEADERS", + "grantTypeInfo": { + "authEndpoint": "test-authorization-endpoint", + "tokenEndpoint": "test-token-endpont", + "clientID": "test-client-id", + "clientSecret": "test-client-secret", + "isPKCE": true, + "codeVerifierMethod": "S256", + "grantType": "AUTHORIZATION_CODE", + "token": "test-token" + } + }, + "preRequestScript": "", + "testScript": "pw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully derives the relevant headers based on the content type\", () => {\n pw.expect(pw.response.body.headers['content-type']).toInclude(\"multipart/form-data\");\n});\n\npw.test(\"Successfully sends the form data in the request body\", () => {\n // Dynamic value\n pw.expect(pw.response.body.data).toBeType(\"string\");\n});", + "body": { + "contentType": "multipart/form-data", + "body": [ + { + "key": "key1", + "value": "value1", + "active": true, + "isFile": false + }, + { + "key": "key2", + "value": [{}], + "active": true, + "isFile": true + } + ] + }, + "requestVariables": [] + } + ], + "auth": { + "authType": "none", + "authActive": true + }, + "headers": [] +} diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/multiple-child-collections-auth-headers-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/multiple-child-collections-auth-headers-coll.json new file mode 100644 index 0000000000..5cff6d5106 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/multiple-child-collections-auth-headers-coll.json @@ -0,0 +1,655 @@ +{ + "v": 3, + "id": "clx1f86hv000010f8szcfya0t", + "name": "Multiple child collections with authorization & headers set at each level", + "folders": [ + { + "v": 3, + "id": "clx1fjgah000110f8a5bs68gd", + "name": "folder-1", + "folders": [ + { + "v": 3, + "id": "clx1fjwmm000410f8l1gkkr1a", + "name": "folder-11", + "folders": [], + "requests": [ + { + "v": "4", + "auth": { + "authType": "inherit", + "password": "testpass", + "username": "testuser", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-11-request", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits authorization/header set at the parent collection level\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\")\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-1\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [ + { + "key": "key", + "value": "Set at folder-11", + "active": true + } + ] + }, + { + "v": 3, + "id": "clx1fjyxm000510f8pv90dt43", + "name": "folder-12", + "folders": [], + "requests": [ + { + "v": "4", + "auth": { + "authType": "none", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-12-request", + "method": "GET", + "params": [], + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-12-request", + "active": true + }, + { + "key": "key", + "value": "Overriden at folder-12-request", + "active": true + } + ], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits/overrides authorization/header set at the parent collection level\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(undefined)\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-12-request\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n pw.expect(pw.response.body.headers[\"Key\"]).toBe(\"Overriden at folder-12-request\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "none", + "authActive": true + }, + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-12", + "active": true + }, + { + "key": "key", + "value": "Set at folder-12", + "active": true + } + ] + }, + { + "v": 3, + "id": "clx1fk1cv000610f88kc3aupy", + "name": "folder-13", + "folders": [], + "requests": [ + { + "v": "4", + "auth": { + "key": "api-key", + "addTo": "HEADERS", + "value": "api-key-value", + "authType": "basic", + "password": "testpass", + "username": "testuser", + "authActive": true, + "grantTypeInfo": { + "token": "", + "isPKCE": true, + "clientID": "sfasfa", + "password": "", + "username": "", + "grantType": "AUTHORIZATION_CODE", + "authEndpoint": "asfafs", + "clientSecret": "sfasfasf", + "tokenEndpoint": "asfa", + "codeVerifierMethod": "S256" + } + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-13-request", + "method": "GET", + "params": [], + "headers": [ + { + "key": "Custom-Header-Request-Level", + "value": "New custom header added at the folder-13-request level", + "active": true + }, + { + "key": "key", + "value": "Overriden at folder-13-request", + "active": true + } + ], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits/overrides authorization/header set at the parent collection level with new header addition\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\")\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-13\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n pw.expect(pw.response.body.headers[\"Key\"]).toBe(\"Overriden at folder-13-request\")\n pw.expect(pw.response.body.headers[\"Custom-Header-Request-Level\"]).toBe(\"New custom header added at the folder-13-request level\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "token": "test-token", + "authType": "bearer", + "authActive": true + }, + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-13", + "active": true + }, + { + "key": "key", + "value": "Set at folder-13", + "active": true + } + ] + } + ], + "requests": [ + { + "v": "4", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-1-request", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits authorization/header set at the parent collection level\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\")\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-1\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-1", + "active": true + } + ] + }, + { + "v": 3, + "id": "clx1fjk9o000210f8j0573pls", + "name": "folder-2", + "folders": [ + { + "v": 3, + "id": "clx1fk516000710f87sfpw6bo", + "name": "folder-21", + "folders": [], + "requests": [ + { + "v": "4", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-21-request", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits authorization/header set at the parent collection level\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(undefined)\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-2\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [ + { + "key": "key", + "value": "Set at folder-21", + "active": true + } + ] + }, + { + "v": 3, + "id": "clx1fk72t000810f8gfwkpi5y", + "name": "folder-22", + "folders": [], + "requests": [ + { + "v": "4", + "auth": { + "authType": "none", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-22-request", + "method": "GET", + "params": [], + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-22-request", + "active": true + }, + { + "key": "key", + "value": "Overriden at folder-22-request", + "active": true + } + ], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits/overrides authorization/header set at the parent collection level\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(undefined)\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-22-request\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n pw.expect(pw.response.body.headers[\"Key\"]).toBe(\"Overriden at folder-22-request\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "none", + "authActive": true + }, + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-22", + "active": true + }, + { + "key": "key", + "value": "Set at folder-22", + "active": true + } + ] + }, + { + "v": 3, + "id": "clx1fk95g000910f8bunhaoo8", + "name": "folder-23", + "folders": [], + "requests": [ + { + "v": "4", + "auth": { + "authType": "basic", + "password": "testpass", + "username": "testuser", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-23-request", + "method": "GET", + "params": [], + "headers": [ + { + "key": "Custom-Header-Request-Level", + "value": "New custom header added at the folder-23-request level", + "active": true + }, + { + "key": "key", + "value": "Overriden at folder-23-request", + "active": true + } + ], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits/overrides authorization/header set at the parent collection level with new header addition\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\")\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-23\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n pw.expect(pw.response.body.headers[\"Key\"]).toBe(\"Overriden at folder-23-request\")\n pw.expect(pw.response.body.headers[\"Custom-Header-Request-Level\"]).toBe(\"New custom header added at the folder-23-request level\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "token": "test-token", + "authType": "bearer", + "password": "testpass", + "username": "testuser", + "authActive": true + }, + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-23", + "active": true + }, + { + "key": "key", + "value": "Set at folder-23", + "active": true + } + ] + } + ], + "requests": [ + { + "v": "4", + "auth": { + "authType": "none", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-2-request", + "method": "GET", + "params": [], + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-2-request", + "active": true + } + ], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits/overrides authorization/header set at the parent collection level\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(undefined)\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-2-request\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "none", + "authActive": true + }, + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-2", + "active": true + } + ] + }, + { + "v": 3, + "id": "clx1fjmlq000310f86o4d3w2o", + "name": "folder-3", + "folders": [ + { + "v": 3, + "id": "clx1iwq0p003e10f8u8zg0p85", + "name": "folder-31", + "folders": [], + "requests": [ + { + "v": "4", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-31-request", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits authorization/header set at the parent collection level\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\")\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-3\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [ + { + "key": "key", + "value": "Set at folder-31", + "active": true + } + ] + }, + { + "v": 3, + "id": "clx1izut7003m10f894ip59zg", + "name": "folder-32", + "folders": [], + "requests": [ + { + "v": "4", + "auth": { + "authType": "none", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-32-request", + "method": "GET", + "params": [], + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-32-request", + "active": true + }, + { + "key": "key", + "value": "Overriden at folder-32-request", + "active": true + } + ], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits/overrides authorization/header set at the parent collection level\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(undefined)\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-32-request\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n pw.expect(pw.response.body.headers[\"Key\"]).toBe(\"Overriden at folder-32-request\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "none", + "authActive": true + }, + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-32", + "active": true + }, + { + "key": "key", + "value": "Set at folder-32", + "active": true + } + ] + }, + { + "v": 3, + "id": "clx1j2ka9003q10f8cdbzpgpg", + "name": "folder-33", + "folders": [], + "requests": [ + { + "v": "4", + "auth": { + "authType": "basic", + "password": "testpass", + "username": "testuser", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-33-request", + "method": "GET", + "params": [], + "headers": [ + { + "key": "Custom-Header-Request-Level", + "value": "New custom header added at the folder-33-request level", + "active": true + }, + { + "key": "key", + "value": "Overriden at folder-33-request", + "active": true + } + ], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits/overrides authorization/header set at the parent collection level with new header addition\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\")\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-33\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n pw.expect(pw.response.body.headers[\"Key\"]).toBe(\"Overriden at folder-33-request\")\n pw.expect(pw.response.body.headers[\"Custom-Header-Request-Level\"]).toBe(\"New custom header added at the folder-33-request level\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "token": "test-token", + "authType": "bearer", + "password": "testpass", + "username": "testuser", + "authActive": true + }, + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-33", + "active": true + }, + { + "key": "key", + "value": "Set at folder-33", + "active": true + } + ] + } + ], + "requests": [ + { + "v": "4", + "auth": { + "authType": "basic", + "password": "testpass", + "username": "testuser", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "folder-3-request", + "method": "GET", + "params": [], + "headers": [ + { + "key": "Custom-Header-Request-Level", + "value": "New custom header added at the folder-3-request level", + "active": true + }, + { + "key": "key", + "value": "Set at folder-3-request", + "active": true + } + ], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits/overrides authorization/header set at the parent collection level with new header addition\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\")\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value overriden at folder-3\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n pw.expect(pw.response.body.headers[\"Key\"]).toBe(\"Set at folder-3-request\")\n pw.expect(pw.response.body.headers[\"Custom-Header-Request-Level\"]).toBe(\"New custom header added at the folder-3-request level\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "key": "testuser", + "addTo": "HEADERS", + "value": "testpass", + "authType": "basic", + "password": "testpass", + "username": "testuser", + "authActive": true + }, + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value overriden at folder-3", + "active": true + } + ] + } + ], + "requests": [ + { + "v": "4", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "root-collection-request", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "https://httpbin.org/get", + "testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully inherits authorization/header set at the parent collection level\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\")\n \n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"Custom header value set at the root collection\")\n pw.expect(pw.response.body.headers[\"Inherited-Header\"]).toBe(\"Inherited header at all levels\")\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "basic", + "password": "testpass", + "username": "testuser", + "authActive": true + }, + "headers": [ + { + "key": "Custom-Header", + "value": "Custom header value set at the root collection", + "active": true + }, + { + "key": "Inherited-Header", + "value": "Inherited header at all levels", + "active": true + } + ] +} diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/notjson-coll.txt b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/notjson-coll.txt similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/notjson-coll.txt rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/notjson-coll.txt diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/oauth2-auth-code-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/oauth2-auth-code-coll.json new file mode 100644 index 0000000000..12c91fbd13 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/oauth2-auth-code-coll.json @@ -0,0 +1,72 @@ +{ + "v": 3, + "name": "OAuth2 Authorization Code Grant Type - Collection", + "folders": [], + "requests": [ + { + "v": "7", + "endpoint": "https://echo.hoppscotch.io", + "name": "oauth2-auth-code-sample-req-pass-by-headers", + "params": [], + "headers": [], + "method": "GET", + "auth": { + "authType": "oauth-2", + "authActive": true, + "addTo": "HEADERS", + "grantTypeInfo": { + "authEndpoint": "test-authorization-endpoint", + "tokenEndpoint": "test-token-endpont", + "clientID": "test-client-id", + "clientSecret": "test-client-secret", + "isPKCE": true, + "codeVerifierMethod": "S256", + "grantType": "AUTHORIZATION_CODE", + "token": "test-token" + } + }, + "preRequestScript": "", + "testScript": "pw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully derives Authorization header from the supplied fields\", ()=> {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBeType(\"string\");\n});", + "body": { + "contentType": null, + "body": null + }, + "requestVariables": [] + }, + { + "v": "7", + "endpoint": "https://echo.hoppscotch.io", + "name": "oauth2-auth-code-sample-req-pass-by-query-params", + "params": [], + "headers": [], + "method": "GET", + "auth": { + "authType": "oauth-2", + "authActive": true, + "addTo": "HEADERS", + "grantTypeInfo": { + "authEndpoint": "test-authorization-endpoint", + "tokenEndpoint": "test-token-endpont", + "clientID": "test-client-id", + "clientSecret": "test-client-secret", + "isPKCE": true, + "codeVerifierMethod": "S256", + "grantType": "AUTHORIZATION_CODE", + "token": "test-token" + } + }, + "preRequestScript": "", + "testScript": "pw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test(\"Successfully derives Authorization header from the supplied fields\", ()=> {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBeType(\"string\");\n});", + "body": { + "contentType": null, + "body": null + }, + "requestVariables": [] + } + ], + "auth": { + "authType": "none", + "authActive": true + }, + "headers": [] +} diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/passes-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/passes-coll.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/passes-coll.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/passes-coll.json diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/pre-req-script-env-var-persistence-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/pre-req-script-env-var-persistence-coll.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/pre-req-script-env-var-persistence-coll.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/pre-req-script-env-var-persistence-coll.json diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/req-body-env-vars-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/req-body-env-vars-coll.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/req-body-env-vars-coll.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/req-body-env-vars-coll.json diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/request-vars-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/request-vars-coll.json new file mode 100644 index 0000000000..7eff0a5a10 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/request-vars-coll.json @@ -0,0 +1,188 @@ +{ + "v": 2, + "name": "Request variables", + "folders": [], + "requests": [ + { + "v": "6", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": "{\n \"<>\": \"<>\"\n}", + "contentType": "application/json" + }, + "name": "request-variables-basic-usage", + "method": "POST", + "params": [ + { + "key": "<>", + "value": "<>", + "active": true + }, + { + "key": "inactive-query-param-key", + "value": "<>", + "active": true + } + ], + "headers": [ + { + "key": "<>", + "value": "<>", + "active": true + }, + { + "key": "inactive-header-key", + "value": "<>", + "active": true + } + ], + "endpoint": "<>", + "testScript": "pw.test(\"Accounts for active request variables\", ()=> {\n pw.expect(pw.response.body.args[\"query-param-key\"]).toBe(\"query-param-value\");\n\n const data = JSON.parse(pw.response.body.data)\n\n pw.expect(data[\"http-body-raw-key\"]).toBe(\"http-body-raw-value\")\n\n pw.expect(pw.response.body.headers[\"custom-header-key\"]).toBe(\"custom-header-value\");\n});\n\npw.test(\"Ignores inactive request variables\", () => {\n pw.expect(pw.response.body.args[\"inactive-query-param-key\"]).toBe(\"\")\n pw.expect(pw.response.body.args[\"inactive-header-key\"]).toBe(undefined)\n})", + "preRequestScript": "", + "requestVariables": [ + { + "key": "url", + "value": "https://echo.hoppscotch.io", + "active": true + }, + { + "key": "method", + "value": "POST", + "active": true + }, + { + "key": "httpBodyRawKey", + "value": "http-body-raw-key", + "active": true + }, + { + "key": "httpBodyRawValue", + "value": "http-body-raw-value", + "active": true + }, + { + "key": "customHeaderKey", + "value": "custom-header-key", + "active": true + }, + { + "key": "customHeaderValue", + "value": "custom-header-value", + "active": true + }, + { + "key": "queryParamKey", + "value": "query-param-key", + "active": true + }, + { + "key": "queryParamValue", + "value": "query-param-value", + "active": true + }, + { + "key": "inactiveQueryParamValue", + "value": "inactive-query-param-value", + "active": false + }, + { + "key": "inactiveHeaderValue", + "value": "inactive-header-value", + "active": false + } + ] + }, + { + "v": "6", + "auth": { + "authType": "none", + "password": "<>", + "username": "<>", + "authActive": true + }, + "body": { + "body": "{\n \"username\": \"<>\",\n \"password\": \"<>\"\n}", + "contentType": "application/json" + }, + "name": "request-variables-alongside-environment-variables", + "method": "POST", + "params": [ + { + "key": "method", + "value": "<>", + "active": true + } + ], + "headers": [ + { + "key": "test-header-key", + "value": "<>", + "active": true + } + ], + "endpoint": "<>/<>", + "testScript": "pw.test(\"The first occurrence is picked for multiple request variable occurrences with the same key.\", () => {\n pw.expect(pw.response.body.args.method).toBe(\"post\");\n});\n\npw.test(\"Request variables support recursive resolution and pick values from secret environment variables\", () => {\n const { username, password } = JSON.parse(pw.response.body.data)\n\n pw.expect(username).toBe(\"username\")\n pw.expect(password).toBe(\"password\")\n\n})\n\npw.test(\"Resolves request variables that are clubbed together\", () => {\n pw.expect(pw.response.body.path).toBe(\"/username/password\")\n})\n\npw.test(\"Request variables are prioritised over environment variables\", () => {\n pw.expect(pw.response.body.headers.host).toBe(\"echo.hoppscotch.io\")\n})\n\npw.test(\"Environment variable is picked if the request variable under the same name is empty\", () => {\n pw.expect(pw.response.body.headers[\"test-header-key\"]).toBe(\"test-header-value\")\n})", + "preRequestScript": "", + "requestVariables": [ + { + "key": "url", + "value": "https://echo.hoppscotch.io", + "active": true + }, + { + "key": "username", + "value": "<>", + "active": true + }, + { + "key": "recursiveBasicAuthUsernameReqVar", + "value": "<>", + "active": true + }, + { + "key": "password", + "value": "<>", + "active": true + }, + { + "key": "recursiveBasicAuthPasswordReqVar", + "value": "<>", + "active": true + }, + { + "key": "method", + "value": "post", + "active": true + }, + { + "key": "method", + "value": "get", + "active": true + }, + { + "key": "method", + "value": "put", + "active": true + }, + { + "key": "path", + "value": "<>/<>", + "active": true + }, + { + "key": "testHeaderValue", + "value": "", + "active": true + } + ] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] +} diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/sample-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/sample-coll.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/sample-coll.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/sample-coll.json diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/secret-envs-coll.json similarity index 68% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-coll.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/secret-envs-coll.json index 2cea26e85a..a79d5a23e8 100644 --- a/packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-coll.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/secret-envs-coll.json @@ -5,8 +5,14 @@ "requests": [ { "v": "3", - "auth": { "authType": "none", "authActive": true }, - "body": { "body": null, "contentType": null }, + "auth": { + "authType": "none", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, "name": "test-secret-headers", "method": "GET", "params": [], @@ -18,13 +24,16 @@ } ], "requestVariables": [], - "endpoint": "<>/headers", - "testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.get(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.get(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})", + "endpoint": "<>/headers", + "testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.get(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.get(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})", "preRequestScript": "const secretHeaderValueFromPreReqScript = pw.env.get(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)" }, { "v": "3", - "auth": { "authType": "none", "authActive": true }, + "auth": { + "authType": "none", + "authActive": true + }, "body": { "body": "{\n \"secretBodyKey\": \"<>\"\n}", "contentType": "application/json" @@ -34,14 +43,20 @@ "params": [], "headers": [], "requestVariables": [], - "endpoint": "<>/post", - "testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})", + "endpoint": "<>/post", + "testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(JSON.parse(pw.response.body.data).secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})", "preRequestScript": "const secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)" }, { "v": "3", - "auth": { "authType": "none", "authActive": true }, - "body": { "body": null, "contentType": null }, + "auth": { + "authType": "none", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, "name": "test-secret-query-params", "method": "GET", "params": [ @@ -53,7 +68,7 @@ ], "headers": [], "requestVariables": [], - "endpoint": "<>/get", + "endpoint": "<>", "testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})", "preRequestScript": "const secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)" }, @@ -65,14 +80,17 @@ "username": "<>", "authActive": true }, - "body": { "body": null, "contentType": null }, + "body": { + "body": null, + "contentType": null + }, "name": "test-secret-basic-auth", "method": "GET", "params": [], "headers": [], "requestVariables": [], - "endpoint": "<>/basic-auth/<>/<>", - "testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});", + "endpoint": "<>/basic-auth/<>/<>", + "testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n // The endpoint at times results in a `502` bad gateway\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});", "preRequestScript": "" }, { @@ -84,30 +102,42 @@ "username": "testuser", "authActive": true }, - "body": { "body": null, "contentType": null }, + "body": { + "body": null, + "contentType": null + }, "name": "test-secret-bearer-auth", "method": "GET", "params": [], "headers": [], "requestVariables": [], - "endpoint": "<>/bearer", - "testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.get(\"secretBearerToken\")\n const preReqSecretBearerToken = pw.env.get(\"preReqSecretBearerToken\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});", + "endpoint": "<>/bearer", + "testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.get(\"secretBearerToken\")\n const preReqSecretBearerToken = pw.env.get(\"preReqSecretBearerToken\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n // Safeguard to prevent test failures due to the endpoint\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});", "preRequestScript": "const secretBearerToken = pw.env.get(\"secretBearerToken\")\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)" }, { "v": "3", - "auth": { "authType": "none", "authActive": true }, - "body": { "body": null, "contentType": null }, + "auth": { + "authType": "none", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, "name": "test-secret-fallback", "method": "GET", "params": [], "headers": [], "requestVariables": [], - "endpoint": "<>", + "endpoint": "<>", "testScript": "pw.test(\"Returns an empty string if the value for a secret environment variable is not found in the system environment\", () => {\n pw.expect(pw.env.get(\"nonExistentValueInSystemEnv\")).toBe(\"\")\n})", "preRequestScript": "" } ], - "auth": { "authType": "inherit", "authActive": false }, + "auth": { + "authType": "inherit", + "authActive": false + }, "headers": [] -} +} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-persistence-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/secret-envs-persistence-coll.json similarity index 83% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-persistence-coll.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/secret-envs-persistence-coll.json index 823eec8016..1de038c5fd 100644 --- a/packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-persistence-coll.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/secret-envs-persistence-coll.json @@ -1,6 +1,6 @@ { "v": 2, - "name": "secret-envs-setters-coll", + "name": "secret-envs-persistence-coll", "folders": [], "requests": [ { @@ -24,8 +24,8 @@ "active": true } ], - "endpoint": "<>/headers", - "testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})", + "endpoint": "<>", + "testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})", "preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)" }, { @@ -49,8 +49,8 @@ "active": true } ], - "endpoint": "<>/headers", - "testScript": "pw.test(\"Value set at the pre-request script takes precedence\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value-overriden\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value-overriden\")\n})", + "endpoint": "<>", + "testScript": "pw.test(\"Value set at the pre-request script takes precedence\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value-overriden\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value-overriden\")\n})", "preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value-overriden\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)" }, { @@ -68,8 +68,8 @@ "params": [], "requestVariables": [], "headers": [], - "endpoint": "<>/post", - "testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})", + "endpoint": "<>/post", + "testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(JSON.parse(pw.response.body.data).secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})", "preRequestScript": "const secretBodyValue = pw.env.get(\"secretBodyValue\")\n\nif (!secretBodyValue) { \n pw.env.set(\"secretBodyValue\", \"secret-body-value\")\n}\n\nconst secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)" }, { @@ -93,7 +93,7 @@ ], "requestVariables": [], "headers": [], - "endpoint": "<>/get", + "endpoint": "<>", "testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})", "preRequestScript": "const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n\nif (!secretQueryParamValue) {\n pw.env.set(\"secretQueryParamValue\", \"secret-query-param-value\")\n}\n\nconst secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)" }, @@ -114,8 +114,8 @@ "params": [], "requestVariables": [], "headers": [], - "endpoint": "<>/basic-auth/<>/<>", - "testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});", + "endpoint": "<>/basic-auth/<>/<>", + "testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n // The endpoint at times results in a `502` bad gateway\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});", "preRequestScript": "let secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n\nlet secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\nif (!secretBasicAuthUsername) {\n pw.env.set(\"secretBasicAuthUsername\", \"test-user\")\n}\n\nif (!secretBasicAuthPassword) {\n pw.env.set(\"secretBasicAuthPassword\", \"test-pass\")\n}" }, { @@ -136,8 +136,8 @@ "params": [], "requestVariables": [], "headers": [], - "endpoint": "<>/bearer", - "testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.resolve(\"<>\")\n const preReqSecretBearerToken = pw.env.resolve(\"<>\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});", + "endpoint": "<>/bearer", + "testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.resolve(\"<>\")\n const preReqSecretBearerToken = pw.env.resolve(\"<>\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n // Safeguard to prevent test failures due to the endpoint\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});", "preRequestScript": "let secretBearerToken = pw.env.resolve(\"<>\")\n\nif (!secretBearerToken) {\n pw.env.set(\"secretBearerToken\", \"test-token\")\n secretBearerToken = pw.env.resolve(\"<>\")\n}\n\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)" } ], @@ -146,4 +146,4 @@ "authActive": false }, "headers": [] -} +} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-persistence-scripting-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/secret-envs-persistence-scripting-coll.json similarity index 54% rename from packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-persistence-scripting-coll.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/secret-envs-persistence-scripting-coll.json index 61dc17cd24..6a103ed448 100644 --- a/packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-persistence-scripting-coll.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/secret-envs-persistence-scripting-coll.json @@ -5,7 +5,7 @@ "requests": [ { "v": "3", - "endpoint": "https://httpbin.org/post", + "endpoint": "https://echo.hoppscotch.io/post", "name": "req", "params": [], "headers": [ @@ -18,7 +18,7 @@ "method": "POST", "auth": { "authType": "none", "authActive": true }, "preRequestScript": "pw.env.set(\"preReqVarOne\", \"pre-req-value-one\")\n\npw.env.set(\"preReqVarTwo\", \"pre-req-value-two\")\n\npw.env.set(\"customHeaderValueFromSecretVar\", \"custom-header-secret-value\")\n\npw.env.set(\"customBodyValue\", \"custom-body-value\")", - "testScript": "pw.test(\"Secret environment value set from the pre-request script takes precedence\", () => {\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(\"pre-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the pre-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request headers that are set in pre-request sccript\", () => {\n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"custom-header-secret-value\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request body that are set in pre-request sccript\", () => {\n pw.expect(pw.response.body.json.key).toBe(\"custom-body-value\")\n})\n\npw.test(\"Secret environment variable set from the post-request script takes precedence\", () => {\n pw.env.set(\"postReqVarOne\", \"post-req-value-one\")\n pw.expect(pw.env.get(\"postReqVarOne\")).toBe(\"post-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the post-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully removes environment variables via the pw.env.unset method\", () => {\n pw.env.unset(\"preReqVarOne\")\n pw.env.unset(\"postReqVarTwo\")\n\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(undefined)\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(undefined)\n})", + "testScript": "pw.test(\"Secret environment value set from the pre-request script takes precedence\", () => {\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(\"pre-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the pre-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request headers that are set in pre-request script\", () => {\n pw.expect(pw.response.body.headers[\"custom-header\"]).toBe(\"custom-header-secret-value\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request body that are set in pre-request script\", () => {\n pw.expect(JSON.parse(pw.response.body.data).key).toBe(\"custom-body-value\")\n})\n\npw.test(\"Secret environment variable set from the post-request script takes precedence\", () => {\n pw.env.set(\"postReqVarOne\", \"post-req-value-one\")\n pw.expect(pw.env.get(\"postReqVarOne\")).toBe(\"post-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the post-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully removes environment variables via the pw.env.unset method\", () => {\n pw.env.unset(\"preReqVarOne\")\n pw.env.unset(\"postReqVarTwo\")\n\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(undefined)\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(undefined)\n})", "body": { "contentType": "application/json", "body": "{\n \"key\": \"<>\"\n}" diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/test-junit-report-export-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/test-junit-report-export-coll.json new file mode 100644 index 0000000000..081f4f3dfb --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/test-junit-report-export-coll.json @@ -0,0 +1,150 @@ +{ + "v": 2, + "name": "test-junit-report-export", + "folders": [ + { + "v": 2, + "name": "assertions", + "folders": [], + "requests": [ + { + "v": "5", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "error", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "https://echo.hoppscotch.io", + "testScript": "pw.test(\"`toBeLevelxxx()` error scenarios\", ()=> {\n pw.expect(\"foo\").toBeLevel2xx();\n pw.expect(\"foo\").not.toBeLevel2xx();\n});\n\npw.test(\"`toBeType()` error scenarios\", () => {\n pw.expect(2).toBeType(\"foo\")\n pw.expect(\"2\").toBeType(\"bar\")\n pw.expect(true).toBeType(\"baz\")\n pw.expect({}).toBeType(\"qux\")\n pw.expect(undefined).toBeType(\"quux\")\n \n pw.expect(2).not.toBeType(\"foo\")\n pw.expect(\"2\").not.toBeType(\"bar\")\n pw.expect(true).not.toBeType(\"baz\")\n pw.expect({}).not.toBeType(\"qux\")\n pw.expect(undefined).not.toBeType(\"quux\")\n})\n\npw.test(\"`toHaveLength()` error scenarios\", () => {\n pw.expect(5).toHaveLength(0)\n pw.expect(true).toHaveLength(0)\n\n pw.expect(5).not.toHaveLength(0)\n pw.expect(true).not.toHaveLength(0)\n\n pw.expect([1, 2, 3, 4]).toHaveLength(\"a\")\n\n pw.expect([1, 2, 3, 4]).not.toHaveLength(\"a\")\n})\n\npw.test(\"`toInclude() error scenarios`\", () => {\n pw.expect(5).not.toInclude(0)\n pw.expect(true).not.toInclude(0)\n\n pw.expect([1, 2, 3, 4]).not.toInclude(null)\n\n pw.expect([1, 2, 3, 4]).not.toInclude(undefined)\n})", + "preRequestScript": "", + "requestVariables": [] + }, + { + "v": "5", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "success", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "https://echo.hoppscotch.io", + "testScript": "\n\n// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check headers\npw.test(\"Check headers\", ()=> {\n pw.expect(pw.response.body.headers[\"accept\"]).toBe(\"application/json, text/plain, */*,image/webp\");\n pw.expect(pw.response.body.headers[\"host\"]).toBe(\"echo.hoppscotch.io\")\n pw.expect(pw.response.body.headers[\"custom-header\"]).toBe(undefined)\n});\n\n// Check status code is 2xx\npw.test(\"Status code is 2xx\", ()=> {\n pw.expect(pw.response.status).toBeLevel2xx();\n});", + "preRequestScript": "", + "requestVariables": [] + }, + { + "v": "5", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "failure", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "https://echo.hoppscotch.io", + "testScript": "\n\n// Check status code is 200\npw.test(\"Simulating failure - Status code is 200\", ()=> {\n pw.expect(pw.response.status).not.toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Simulating failure - Check headers\", ()=> {\n pw.expect(pw.response.body.headers[\"accept\"]).not.toBe(\"application/json, text/plain, */*\");\n pw.expect(pw.response.body.headers[\"host\"]).not.toBe(\"httpbin.org\")\n pw.expect(pw.response.body.headers[\"custom-header\"]).not.toBe(\"value\")\n});\n\n// Check status code is 2xx\npw.test(\"Simulating failure - Status code is 2xx\", ()=> {\n pw.expect(pw.response.status).not.toBeLevel2xx();\n});", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] + }, + { + "v": 2, + "name": "request-level-errors", + "folders": [], + "requests": [ + { + "v": "5", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "invalid-url", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "invalid-url", + "testScript": "pw.test(\"`toBeLevelxxx()` error scenarios\", ()=> {\n pw.expect(\"foo\").toBeLevel2xx();\n pw.expect(\"foo\").not.toBeLevel2xx();\n});\n\npw.test(\"`toBeType()` error scenarios\", () => {\n pw.expect(2).toBeType(\"foo\")\n pw.expect(\"2\").toBeType(\"bar\")\n pw.expect(true).toBeType(\"baz\")\n pw.expect({}).toBeType(\"qux\")\n pw.expect(undefined).toBeType(\"quux\")\n \n pw.expect(2).not.toBeType(\"foo\")\n pw.expect(\"2\").not.toBeType(\"bar\")\n pw.expect(true).not.toBeType(\"baz\")\n pw.expect({}).not.toBeType(\"qux\")\n pw.expect(undefined).not.toBeType(\"quux\")\n})\n\npw.test(\"`toHaveLength()` error scenarios\", () => {\n pw.expect(5).toHaveLength(0)\n pw.expect(true).toHaveLength(0)\n\n pw.expect(5).not.toHaveLength(0)\n pw.expect(true).not.toHaveLength(0)\n\n pw.expect([1, 2, 3, 4]).toHaveLength(\"a\")\n\n pw.expect([1, 2, 3, 4]).not.toHaveLength(\"a\")\n})\n\npw.test(\"`toInclude() error scenarios`\", () => {\n pw.expect(5).not.toInclude(0)\n pw.expect(true).not.toInclude(0)\n\n pw.expect([1, 2, 3, 4]).not.toInclude(null)\n\n pw.expect([1, 2, 3, 4]).not.toInclude(undefined)\n})", + "preRequestScript": "", + "requestVariables": [] + }, + { + "v": "5", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": null, + "contentType": null + }, + "name": "test-script-reference-error", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "invalid-url", + "testScript": "pw.test(\"Reference error\", () => {\n pw.expect(status).toBe(200);\n})", + "preRequestScript": "", + "requestVariables": [] + }, + { + "v": "5", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "body": "{\n \"key\": \"<>\"\n}", + "contentType": "application/json" + }, + "name": "non-existent-env-var", + "method": "POST", + "params": [], + "headers": [], + "endpoint": "https://echo.hoppscotch.io", + "testScript": "pw.test(\"`toBeLevelxxx()` error scenarios\", ()=> {\n pw.expect(\"foo\").toBeLevel2xx();\n pw.expect(\"foo\").not.toBeLevel2xx();\n});\n\npw.test(\"`toBeType()` error scenarios\", () => {\n pw.expect(2).toBeType(\"foo\")\n pw.expect(\"2\").toBeType(\"bar\")\n pw.expect(true).toBeType(\"baz\")\n pw.expect({}).toBeType(\"qux\")\n pw.expect(undefined).toBeType(\"quux\")\n \n pw.expect(2).not.toBeType(\"foo\")\n pw.expect(\"2\").not.toBeType(\"bar\")\n pw.expect(true).not.toBeType(\"baz\")\n pw.expect({}).not.toBeType(\"qux\")\n pw.expect(undefined).not.toBeType(\"quux\")\n})\n\npw.test(\"`toHaveLength()` error scenarios\", () => {\n pw.expect(5).toHaveLength(0)\n pw.expect(true).toHaveLength(0)\n\n pw.expect(5).not.toHaveLength(0)\n pw.expect(true).not.toHaveLength(0)\n\n pw.expect([1, 2, 3, 4]).toHaveLength(\"a\")\n\n pw.expect([1, 2, 3, 4]).not.toHaveLength(\"a\")\n})\n\npw.test(\"`toInclude() error scenarios`\", () => {\n pw.expect(5).not.toInclude(0)\n pw.expect(true).not.toInclude(0)\n\n pw.expect([1, 2, 3, 4]).not.toInclude(null)\n\n pw.expect([1, 2, 3, 4]).not.toInclude(undefined)\n})", + "preRequestScript": "", + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] + } + ], + "requests": [], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] +} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/aws-signature-auth-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/aws-signature-auth-envs.json new file mode 100644 index 0000000000..534bf7113f --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/aws-signature-auth-envs.json @@ -0,0 +1,35 @@ +{ + "v": 1, + "id": "cm0dsn3v70004p4qk3l9b7sjm", + "name": "AWS Signature - environments", + "variables": [ + { + "key": "awsRegion", + "value": "us-east-1", + "secret": false + }, + { + "key": "serviceName", + "value": "s3", + "secret": false + }, + { + "key": "accessKey", + "value": "test-access-key", + "secret": true + }, + { + "key": "secretKey", + "secret": true + }, + { + "key": "url", + "value": "https://echo.hoppscotch.io", + "secret": false + }, + { + "key": "serviceToken", + "secret": true + } + ] +} diff --git a/packages/hoppscotch-cli/src/__tests__/samples/environments/bulk-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/bulk-envs.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/environments/bulk-envs.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/bulk-envs.json diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/digest-auth-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/digest-auth-envs.json new file mode 100644 index 0000000000..eacbcbc07c --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/digest-auth-envs.json @@ -0,0 +1,21 @@ +{ + "v": 1, + "id": "cm0dsn3v70004p4qk3l9b7sjm", + "name": "Digest Auth - environments", + "variables": [ + { + "key": "username", + "value": "admin", + "secret": true + }, + { + "key": "password", + "value": "admin", + "secret": true + }, + { + "key": "url", + "value": "https://test.insightres.org/digest/" + } + ] +} diff --git a/packages/hoppscotch-cli/src/__tests__/samples/environments/env-flag-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-flag-envs.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/environments/env-flag-envs.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-flag-envs.json diff --git a/packages/hoppscotch-cli/src/__tests__/samples/environments/env-v0.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-v0.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/environments/env-v0.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-v0.json diff --git a/packages/hoppscotch-cli/src/__tests__/samples/environments/env-v1.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-v1.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/environments/env-v1.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-v1.json diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/iteration-data-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/iteration-data-envs.json new file mode 100644 index 0000000000..bea639355a --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/iteration-data-envs.json @@ -0,0 +1,18 @@ +{ + "v": 0, + "name": "Iteration data environments", + "variables": [ + { + "key": "URL", + "value": "https://httpbin.org/get" + }, + { + "key": "BODY_KEY", + "value": "overriden-body-key-at-environment" + }, + { + "key": "BODY_VALUE", + "value": "overriden-body-value-at-environment" + } + ] +} diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/iteration-data-export.csv b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/iteration-data-export.csv new file mode 100644 index 0000000000..5caf994ed4 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/iteration-data-export.csv @@ -0,0 +1,4 @@ +URL,BODY_KEY,BODY_VALUE +https://echo.hoppscotch.io/1,,body_value1 +https://echo.hoppscotch.io/2,,body_value2 +https://echo.hoppscotch.io/3,,body_value3 diff --git a/packages/hoppscotch-cli/src/__tests__/samples/environments/malformed-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/malformed-envs.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/environments/malformed-envs.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/malformed-envs.json diff --git a/packages/hoppscotch-cli/src/__tests__/samples/environments/req-body-env-vars-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/req-body-env-vars-envs.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/environments/req-body-env-vars-envs.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/req-body-env-vars-envs.json diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/request-vars-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/request-vars-envs.json new file mode 100644 index 0000000000..844af0108b --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/request-vars-envs.json @@ -0,0 +1,34 @@ +{ + "v": 1, + "id": "cm00r7kpb0006mbd2nq1560w6", + "name": "Request variables alongside environment variables", + "variables": [ + { + "key": "url", + "value": "https://echo.hoppscotch.io", + "secret": false + }, + { + "key": "secretBasicAuthPasswordEnvVar", + "secret": true + }, + { + "key": "secretBasicAuthUsernameEnvVar", + "value": "username", + "secret": true + }, + { + "key": "username", + "secret": true + }, + { + "key": "password", + "secret": true + }, + { + "key": "testHeaderValue", + "value": "test-header-value", + "secret": false + } + ] +} diff --git a/packages/hoppscotch-cli/src/__tests__/samples/environments/secret-envs-persistence-scripting-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs-persistence-scripting-envs.json similarity index 100% rename from packages/hoppscotch-cli/src/__tests__/samples/environments/secret-envs-persistence-scripting-envs.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs-persistence-scripting-envs.json diff --git a/packages/hoppscotch-cli/src/__tests__/samples/environments/secret-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs.json similarity index 81% rename from packages/hoppscotch-cli/src/__tests__/samples/environments/secret-envs.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs.json index 53d33bb354..266f821c8a 100644 --- a/packages/hoppscotch-cli/src/__tests__/samples/environments/secret-envs.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs.json @@ -32,7 +32,12 @@ "secret": true }, { - "key": "baseURL", + "key": "echoHoppBaseURL", + "value": "https://echo.hoppscotch.io", + "secret": false + }, + { + "key": "httpbinBaseURL", "value": "https://httpbin.org", "secret": false } diff --git a/packages/hoppscotch-cli/src/__tests__/samples/environments/secret-supplied-values-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-supplied-values-envs.json similarity index 85% rename from packages/hoppscotch-cli/src/__tests__/samples/environments/secret-supplied-values-envs.json rename to packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-supplied-values-envs.json index fec73a2939..88155f84e7 100644 --- a/packages/hoppscotch-cli/src/__tests__/samples/environments/secret-supplied-values-envs.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-supplied-values-envs.json @@ -38,7 +38,12 @@ "secret": true }, { - "key": "baseURL", + "key": "echoHoppBaseURL", + "value": "https://echo.hoppscotch.io", + "secret": false + }, + { + "key": "httpbinBaseURL", "value": "https://httpbin.org", "secret": false } diff --git a/packages/hoppscotch-cli/src/__tests__/functions/request/requestRunner.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/request/requestRunner.spec.ts index e1df4a2dfb..7fb45aca7f 100644 --- a/packages/hoppscotch-cli/src/__tests__/functions/request/requestRunner.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/functions/request/requestRunner.spec.ts @@ -15,7 +15,6 @@ describe("requestRunner", () => { }; beforeEach(() => { - SAMPLE_REQUEST_CONFIG.supported = false; SAMPLE_REQUEST_CONFIG.url = "https://example.com"; SAMPLE_REQUEST_CONFIG.method = "GET"; jest.clearAllMocks(); @@ -70,7 +69,6 @@ describe("requestRunner", () => { it("Should handle axios-error with request info.", () => { jest.spyOn(axios, "isAxiosError").mockReturnValue(true); - SAMPLE_REQUEST_CONFIG.supported = true; (axios as unknown as jest.Mock).mockRejectedValueOnce({ name: "name", message: "message", @@ -91,7 +89,6 @@ describe("requestRunner", () => { }); it("Should successfully execute.", () => { - SAMPLE_REQUEST_CONFIG.supported = true; (axios as unknown as jest.Mock).mockResolvedValue({ data: "data", status: 200, diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v1-req-v0.json b/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v1-req-v0.json deleted file mode 100644 index 6393ba6ad3..0000000000 --- a/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v1-req-v0.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "v": 1, - "name": "coll-v1", - "folders": [], - "requests": [ - { - "url": "https://httpbin.org", - "path": "/get", - "headers": [ - { "key": "Inactive-Header", "value": "Inactive Header", "active": false }, - { "key": "Authorization", "value": "Bearer token123", "active": true } - ], - "params": [ - { "key": "key", "value": "value", "active": true }, - { "key": "inactive-key", "value": "inactive-param", "active": false } - ], - "name": "req-v0", - "method": "GET", - "preRequestScript": "", - "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})", - "contentType": "application/json", - "body": "", - "auth": "Bearer Token", - "bearerToken": "token123" - } - ] -} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v1-req-v1.json b/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v1-req-v1.json deleted file mode 100644 index 61f9adb52b..0000000000 --- a/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v1-req-v1.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "v": 1, - "name": "coll-v1", - "folders": [], - "requests": [ - { - "v": "1", - "endpoint": "https://httpbin.org/get", - "headers": [ - { - "key": "Inactive-Header", - "value": "Inactive Header", - "active": false - }, - { - "key": "Authorization", - "value": "Bearer token123", - "active": true - } - ], - "params": [ - { - "key": "key", - "value": "value", - "active": true - }, - { - "key": "inactive-key", - "value": "inactive-param", - "active": false - } - ], - "name": "req-v1", - "method": "GET", - "preRequestScript": "", - "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})", - "body": { - "contentType": null, - "body": null - }, - "auth": { - "authType": "bearer", - "authActive": true, - "token": "token123" - } - } - ] -} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v2-req-v2.json b/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v2-req-v2.json deleted file mode 100644 index 780373f828..0000000000 --- a/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v2-req-v2.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "v": 2, - "name": "coll-v2", - "folders": [], - "requests": [ - { - "v": "2", - "endpoint": "https://httpbin.org/get", - "headers": [ - { - "key": "Inactive-Header", - "value": "Inactive Header", - "active": false - }, - { - "key": "Authorization", - "value": "Bearer token123", - "active": true - } - ], - "params": [ - { - "key": "key", - "value": "value", - "active": true - }, - { - "key": "inactive-key", - "value": "inactive-param", - "active": false - } - ], - "name": "req-v2", - "method": "GET", - "preRequestScript": "", - "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})", - "body": { - "contentType": null, - "body": null - }, - "auth": { - "authType": "bearer", - "authActive": true, - "token": "token123" - }, - "requestVariables": [] - } - ], - "auth": { - "authType": "inherit", - "authActive": true - }, - "headers": [] -} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v2-req-v3.json b/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v2-req-v3.json deleted file mode 100644 index fcce134a8d..0000000000 --- a/packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v2-req-v3.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "v": 2, - "name": "coll-v2", - "folders": [], - "requests": [ - { - "v": "3", - "endpoint": "https://httpbin.org/get", - "headers": [ - { - "key": "Inactive-Header", - "value": "Inactive Header", - "active": false - }, - { - "key": "Authorization", - "value": "Bearer token123", - "active": true - } - ], - "params": [ - { - "key": "key", - "value": "value", - "active": true - }, - { - "key": "inactive-key", - "value": "inactive-param", - "active": false - } - ], - "name": "req-v3", - "method": "GET", - "preRequestScript": "", - "testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})", - "body": { - "contentType": null, - "body": null - }, - "auth": { - "authType": "bearer", - "authActive": true, - "token": "token123" - }, - "requestVariables": [] - } - ], - "auth": { - "authType": "inherit", - "authActive": true - }, - "headers": [] -} \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts b/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts new file mode 100644 index 0000000000..5c52c1c8ec --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts @@ -0,0 +1,1462 @@ +import { + CollectionSchemaVersion, + Environment, + EnvironmentSchemaVersion, + HoppCollection, + RESTReqSchemaVersion, +} from "@hoppscotch/data"; + +import { + WorkspaceCollection, + WorkspaceEnvironment, +} from "../../../utils/workspace-access"; + +export const WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: WorkspaceCollection[] = + [ + { + id: "clx1ldkzs005t10f8rp5u60q7", + data: '{"auth":{"token":"BearerToken","authType":"bearer","authActive":true},"headers":[{"key":"X-Test-Header","value":"Set at root collection","active":true,"description":""}]}', + title: "CollectionA", + parentID: null, + folders: [ + { + id: "clx1ldkzs005v10f86b9wx4yc", + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[]}', + title: "FolderA", + parentID: "clx1ldkzs005t10f8rp5u60q7", + folders: [ + { + id: "clx1ldkzt005x10f8i0u5lzgj", + data: '{"auth":{"key":"key","addTo":"HEADERS","value":"test-key","authType":"api-key","authActive":true},"headers":[{"key":"X-Test-Header","value":"Overriden at FolderB","active":true}]}', + title: "FolderB", + parentID: "clx1ldkzs005v10f86b9wx4yc", + folders: [ + { + id: "clx1ldkzu005z10f880zx17bg", + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[]}', + title: "FolderC", + parentID: "clx1ldkzt005x10f8i0u5lzgj", + folders: [], + requests: [ + { + id: "clx1ldkzu006010f820vzy13v", + collectionID: "clx1ldkzu005z10f880zx17bg", + teamID: "clws3hg58000011o8h07glsb1", + title: "RequestD", + request: + '{"v":"3","auth":{"authType":"basic","password":"password","username":"username","authActive":true},"body":{"body":null,"contentType":null},"name":"RequestD","method":"GET","params":[],"headers":[{"key":"X-Test-Header","value":"Overriden at RequestD","active":true}],"endpoint":"https://echo.hoppscotch.io","testScript":"pw.test(\\"Overrides auth and headers set at the parent folder\\", ()=> {\\n pw.expect(pw.response.body.headers[\\"x-test-header\\"]).toBe(\\"Overriden at RequestD\\");\\n pw.expect(pw.response.body.headers[\\"authorization\\"]).toBe(\\"Basic dXNlcm5hbWU6cGFzc3dvcmQ=\\");\\n});","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + ], + requests: [ + { + id: "clx1ldkzt005y10f82dl8ni8d", + collectionID: "clx1ldkzt005x10f8i0u5lzgj", + teamID: "clws3hg58000011o8h07glsb1", + title: "RequestC", + request: + '{"v":"3","auth":{"authType":"inherit","authActive":true},"body":{"body":null,"contentType":null},"name":"RequestC","method":"GET","params":[],"headers":[],"endpoint":"https://echo.hoppscotch.io","testScript":"pw.test(\\"Correctly inherits auth and headers from the parent folder\\", ()=> {\\n pw.expect(pw.response.body.headers[\\"x-test-header\\"]).toBe(\\"Overriden at FolderB\\");\\n pw.expect(pw.response.body.headers[\\"key\\"]).toBe(\\"test-key\\");\\n});","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + ], + requests: [ + { + id: "clx1ldkzs005w10f8pc2v2boh", + collectionID: "clx1ldkzs005v10f86b9wx4yc", + teamID: "clws3hg58000011o8h07glsb1", + title: "RequestB", + request: + '{"v":"3","id":"clpttpdq00003qp16kut6doqv","auth":{"authType":"inherit","authActive":true},"body":{"body":null,"contentType":null},"name":"RequestB","method":"GET","params":[],"headers":[],"endpoint":"https://echo.hoppscotch.io","testScript":"pw.test(\\"Correctly inherits auth and headers from the parent folder\\", ()=> {\\n pw.expect(pw.response.body.headers[\\"x-test-header\\"]).toBe(\\"Set at root collection\\");\\n pw.expect(pw.response.body.headers[\\"authorization\\"]).toBe(\\"Bearer BearerToken\\");\\n});","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + ], + requests: [ + { + id: "clx1ldkzs005u10f82xd5ho3l", + collectionID: "clx1ldkzs005t10f8rp5u60q7", + teamID: "clws3hg58000011o8h07glsb1", + title: "RequestA", + request: `{"v":"${RESTReqSchemaVersion}","id":"clpttpdq00003qp16kut6doqv","auth":{"authType":"inherit","authActive":true},"body":{"body":null,"contentType":null},"name":"RequestA","method":"GET","params":[],"headers":[],"endpoint":"https://echo.hoppscotch.io","testScript":"pw.test(\\"Correctly inherits auth and headers from the root collection\\", ()=> {\\n pw.expect(pw.response.body.headers[\\"x-test-header\\"]).toBe(\\"Set at root collection\\");\\n pw.expect(pw.response.body.headers[\\"authorization\\"]).toBe(\\"Bearer BearerToken\\");\\n});","preRequestScript":"","requestVariables":[],"responses":{}}`, + }, + ], + }, + ]; + +export const TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: HoppCollection[] = + [ + { + v: CollectionSchemaVersion, + id: "clx1ldkzs005t10f8rp5u60q7", + name: "CollectionA", + folders: [ + { + v: CollectionSchemaVersion, + id: "clx1ldkzs005v10f86b9wx4yc", + name: "FolderA", + folders: [ + { + v: CollectionSchemaVersion, + id: "clx1ldkzt005x10f8i0u5lzgj", + name: "FolderB", + folders: [ + { + v: CollectionSchemaVersion, + id: "clx1ldkzu005z10f880zx17bg", + name: "FolderC", + folders: [], + requests: [ + { + v: "3", + auth: { + authType: "basic", + password: "password", + username: "username", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "RequestD", + method: "GET", + params: [], + headers: [ + { + key: "X-Test-Header", + value: "Overriden at RequestD", + active: true, + }, + ], + endpoint: "https://echo.hoppscotch.io", + testScript: + 'pw.test("Overrides auth and headers set at the parent folder", ()=> {\n pw.expect(pw.response.body.headers["x-test-header"]).toBe("Overriden at RequestD");\n pw.expect(pw.response.body.headers["authorization"]).toBe("Basic dXNlcm5hbWU6cGFzc3dvcmQ=");\n});', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "inherit", + authActive: true, + }, + headers: [], + }, + ], + requests: [ + { + v: "3", + auth: { + authType: "inherit", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "RequestC", + method: "GET", + params: [], + headers: [], + endpoint: "https://echo.hoppscotch.io", + testScript: + 'pw.test("Correctly inherits auth and headers from the parent folder", ()=> {\n pw.expect(pw.response.body.headers["x-test-header"]).toBe("Overriden at FolderB");\n pw.expect(pw.response.body.headers["key"]).toBe("test-key");\n});', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + key: "key", + addTo: "HEADERS", + value: "test-key", + authType: "api-key", + authActive: true, + }, + headers: [ + { + key: "X-Test-Header", + value: "Overriden at FolderB", + active: true, + description: "", + }, + ], + }, + ], + requests: [ + { + v: "3", + id: "clpttpdq00003qp16kut6doqv", + auth: { + authType: "inherit", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "RequestB", + method: "GET", + params: [], + headers: [], + endpoint: "https://echo.hoppscotch.io", + testScript: + 'pw.test("Correctly inherits auth and headers from the parent folder", ()=> {\n pw.expect(pw.response.body.headers["x-test-header"]).toBe("Set at root collection");\n pw.expect(pw.response.body.headers["authorization"]).toBe("Bearer BearerToken");\n});', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "inherit", + authActive: true, + }, + headers: [], + }, + ], + requests: [ + { + v: RESTReqSchemaVersion, + id: "clpttpdq00003qp16kut6doqv", + auth: { + authType: "inherit", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "RequestA", + method: "GET", + params: [], + headers: [], + endpoint: "https://echo.hoppscotch.io", + testScript: + 'pw.test("Correctly inherits auth and headers from the root collection", ()=> {\n pw.expect(pw.response.body.headers["x-test-header"]).toBe("Set at root collection");\n pw.expect(pw.response.body.headers["authorization"]).toBe("Bearer BearerToken");\n});', + preRequestScript: "", + requestVariables: [], + responses: {}, + }, + ], + auth: { + token: "BearerToken", + authType: "bearer", + authActive: true, + }, + headers: [ + { + key: "X-Test-Header", + value: "Set at root collection", + active: true, + description: "", + }, + ], + }, + ]; + +export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: WorkspaceCollection[] = + [ + { + id: "clx1f86hv000010f8szcfya0t", + data: '{"auth":{"authType":"basic","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value set at the root collection","active":true},{"key":"Inherited-Header","value":"Inherited header at all levels","active":true}]}', + title: + "Multiple child collections with authorization & headers set at each level", + parentID: null, + folders: [ + { + id: "clx1fjgah000110f8a5bs68gd", + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-1","active":true}]}', + title: "folder-1", + parentID: "clx1f86hv000010f8szcfya0t", + folders: [ + { + id: "clx1fjwmm000410f8l1gkkr1a", + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"key","value":"Set at folder-11","active":true}]}', + title: "folder-11", + parentID: "clx1fjgah000110f8a5bs68gd", + folders: [], + requests: [ + { + id: "clx1gjo1q000p10f8tc3x2u50", + collectionID: "clx1fjwmm000410f8l1gkkr1a", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-11-request", + request: + '{"v":"4","auth":{"authType":"inherit","password":"testpass","username":"testuser","authActive":true},"body":{"body":null,"contentType":null},"name":"folder-11-request","method":"GET","params":[],"headers":[],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits authorization/header set at the parent collection level\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(\\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\\")\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-1\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + { + id: "clx1fjyxm000510f8pv90dt43", + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-12","active":true},{"key":"key","value":"Set at folder-12","active":true}]}', + title: "folder-12", + parentID: "clx1fjgah000110f8a5bs68gd", + folders: [], + requests: [ + { + id: "clx1glkt5000u10f88q51ioj8", + collectionID: "clx1fjyxm000510f8pv90dt43", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-12-request", + request: + '{"v":"4","auth":{"authType":"none","authActive":true},"body":{"body":null,"contentType":null},"name":"folder-12-request","method":"GET","params":[],"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-12-request","active":true},{"key":"key","value":"Overriden at folder-12-request","active":true}],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits/overrides authorization/header set at the parent collection level\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(undefined)\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-12-request\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n pw.expect(pw.response.body.headers[\\"Key\\"]).toBe(\\"Overriden at folder-12-request\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + { + id: "clx1fk1cv000610f88kc3aupy", + data: '{"auth":{"token":"test-token","authType":"bearer","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-13","active":true},{"key":"key","value":"Set at folder-13","active":true}]}', + title: "folder-13", + parentID: "clx1fjgah000110f8a5bs68gd", + folders: [], + requests: [ + { + id: "clx1grfir001510f8c4ttiazq", + collectionID: "clx1fk1cv000610f88kc3aupy", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-13-request", + request: + '{"v":"4","auth":{"key":"api-key","addTo":"HEADERS","value":"api-key-value","authType":"basic","password":"testpass","username":"testuser","authActive":true,"grantTypeInfo":{"token":"","isPKCE":true,"clientID":"sfasfa","password":"","username":"","grantType":"AUTHORIZATION_CODE","authEndpoint":"asfafs","clientSecret":"sfasfasf","tokenEndpoint":"asfa","codeVerifierMethod":"S256"}},"body":{"body":null,"contentType":null},"name":"folder-13-request","method":"GET","params":[],"headers":[{"key":"Custom-Header-Request-Level","value":"New custom header added at the folder-13-request level","active":true},{"key":"key","value":"Overriden at folder-13-request","active":true}],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits/overrides authorization/header set at the parent collection level with new header addition\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(\\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\\")\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-13\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n pw.expect(pw.response.body.headers[\\"Key\\"]).toBe(\\"Overriden at folder-13-request\\")\\n pw.expect(pw.response.body.headers[\\"Custom-Header-Request-Level\\"]).toBe(\\"New custom header added at the folder-13-request level\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + ], + requests: [ + { + id: "clx1gebpx000k10f8andzw36z", + collectionID: "clx1fjgah000110f8a5bs68gd", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-1-request", + request: + '{"v":"4","auth":{"authType":"inherit","authActive":true},"body":{"body":null,"contentType":null},"name":"folder-1-request","method":"GET","params":[],"headers":[],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits authorization/header set at the parent collection level\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(\\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\\")\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-1\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + { + id: "clx1fjk9o000210f8j0573pls", + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-2","active":true}]}', + title: "folder-2", + parentID: "clx1f86hv000010f8szcfya0t", + folders: [ + { + id: "clx1fk516000710f87sfpw6bo", + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"key","value":"Set at folder-21","active":true}]}', + title: "folder-21", + parentID: "clx1fjk9o000210f8j0573pls", + folders: [], + requests: [ + { + id: "clx1hfegy001j10f8ywbozysk", + collectionID: "clx1fk516000710f87sfpw6bo", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-21-request", + request: + '{"v":"4","auth":{"authType":"inherit","authActive":true},"body":{"body":null,"contentType":null},"name":"folder-21-request","method":"GET","params":[],"headers":[],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits authorization/header set at the parent collection level\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(undefined)\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-2\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + { + id: "clx1fk72t000810f8gfwkpi5y", + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-22","active":true},{"key":"key","value":"Set at folder-22","active":true}]}', + title: "folder-22", + parentID: "clx1fjk9o000210f8j0573pls", + folders: [], + requests: [ + { + id: "clx1ibfre002k10f86brcb2aa", + collectionID: "clx1fk72t000810f8gfwkpi5y", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-22-request", + request: + '{"v":"4","auth":{"authType":"none","authActive":true},"body":{"body":null,"contentType":null},"name":"folder-22-request","method":"GET","params":[],"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-22-request","active":true},{"key":"key","value":"Overriden at folder-22-request","active":true}],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits/overrides authorization/header set at the parent collection level\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(undefined)\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-22-request\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n pw.expect(pw.response.body.headers[\\"Key\\"]).toBe(\\"Overriden at folder-22-request\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + { + id: "clx1fk95g000910f8bunhaoo8", + data: '{"auth":{"token":"test-token","authType":"bearer","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-23","active":true},{"key":"key","value":"Set at folder-23","active":true}]}', + title: "folder-23", + parentID: "clx1fjk9o000210f8j0573pls", + folders: [], + requests: [ + { + id: "clx1if4w6002n10f8xe4gnf0w", + collectionID: "clx1fk95g000910f8bunhaoo8", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-23-request", + request: + '{"v":"4","auth":{"authType":"basic","password":"testpass","username":"testuser","authActive":true},"body":{"body":null,"contentType":null},"name":"folder-23-request","method":"GET","params":[],"headers":[{"key":"Custom-Header-Request-Level","value":"New custom header added at the folder-23-request level","active":true},{"key":"key","value":"Overriden at folder-23-request","active":true}],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits/overrides authorization/header set at the parent collection level with new header addition\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(\\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\\")\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-23\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n pw.expect(pw.response.body.headers[\\"Key\\"]).toBe(\\"Overriden at folder-23-request\\")\\n pw.expect(pw.response.body.headers[\\"Custom-Header-Request-Level\\"]).toBe(\\"New custom header added at the folder-23-request level\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + ], + requests: [ + { + id: "clx1hbtdj001g10f8y71y869s", + collectionID: "clx1fjk9o000210f8j0573pls", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-2-request", + request: + '{"v":"4","auth":{"authType":"none","authActive":true},"body":{"body":null,"contentType":null},"name":"folder-2-request","method":"GET","params":[],"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-2-request","active":true}],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits/overrides authorization/header set at the parent collection level\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(undefined)\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-2-request\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + { + id: "clx1fjmlq000310f86o4d3w2o", + data: '{"auth":{"key":"testuser","addTo":"HEADERS","value":"testpass","authType":"basic","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-3","active":true}]}', + title: "folder-3", + parentID: "clx1f86hv000010f8szcfya0t", + folders: [ + { + id: "clx1iwq0p003e10f8u8zg0p85", + data: '{"auth":{"authType":"inherit","authActive":true},"headers":[{"key":"key","value":"Set at folder-31","active":true}]}', + title: "folder-31", + parentID: "clx1fjmlq000310f86o4d3w2o", + folders: [], + requests: [ + { + id: "clx1ixdiv003f10f8j6ni375m", + collectionID: "clx1iwq0p003e10f8u8zg0p85", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-31-request", + request: + '{"v":"4","auth":{"authType":"inherit","authActive":true},"body":{"body":null,"contentType":null},"name":"folder-31-request","method":"GET","params":[],"headers":[],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits authorization/header set at the parent collection level\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(\\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\\")\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-3\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + { + id: "clx1izut7003m10f894ip59zg", + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-32","active":true},{"key":"key","value":"Set at folder-32","active":true}]}', + title: "folder-32", + parentID: "clx1fjmlq000310f86o4d3w2o", + folders: [], + requests: [ + { + id: "clx1j01dg003n10f8e34khl6v", + collectionID: "clx1izut7003m10f894ip59zg", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-32-request", + request: + '{"v":"4","auth":{"authType":"none","authActive":true},"body":{"body":null,"contentType":null},"name":"folder-32-request","method":"GET","params":[],"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-32-request","active":true},{"key":"key","value":"Overriden at folder-32-request","active":true}],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits/overrides authorization/header set at the parent collection level\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(undefined)\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-32-request\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n pw.expect(pw.response.body.headers[\\"Key\\"]).toBe(\\"Overriden at folder-32-request\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + { + id: "clx1j2ka9003q10f8cdbzpgpg", + data: '{"auth":{"token":"test-token","authType":"bearer","password":"testpass","username":"testuser","authActive":true},"headers":[{"key":"Custom-Header","value":"Custom header value overriden at folder-33","active":true},{"key":"key","value":"Set at folder-33","active":true}]}', + title: "folder-33", + parentID: "clx1fjmlq000310f86o4d3w2o", + folders: [], + requests: [ + { + id: "clx1j361a003r10f8oly5m2n6", + collectionID: "clx1j2ka9003q10f8cdbzpgpg", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-33-request", + request: + '{"v":"4","auth":{"authType":"basic","password":"testpass","username":"testuser","authActive":true},"body":{"body":null,"contentType":null},"name":"folder-33-request","method":"GET","params":[],"headers":[{"key":"Custom-Header-Request-Level","value":"New custom header added at the folder-33-request level","active":true},{"key":"key","value":"Overriden at folder-33-request","active":true}],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits/overrides authorization/header set at the parent collection level with new header addition\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(\\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\\")\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-33\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n pw.expect(pw.response.body.headers[\\"Key\\"]).toBe(\\"Overriden at folder-33-request\\")\\n pw.expect(pw.response.body.headers[\\"Custom-Header-Request-Level\\"]).toBe(\\"New custom header added at the folder-33-request level\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + ], + requests: [ + { + id: "clx1jk1nq004y10f8fhtxvs02", + collectionID: "clx1fjmlq000310f86o4d3w2o", + teamID: "clws3hg58000011o8h07glsb1", + title: "folder-3-request", + request: + '{"v":"4","auth":{"authType":"basic","password":"testpass","username":"testuser","authActive":true},"body":{"body":null,"contentType":null},"name":"folder-3-request","method":"GET","params":[],"headers":[{"key":"Custom-Header-Request-Level","value":"New custom header added at the folder-3-request level","active":true},{"key":"key","value":"Set at folder-3-request","active":true}],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits/overrides authorization/header set at the parent collection level with new header addition\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(\\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\\")\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value overriden at folder-3\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n pw.expect(pw.response.body.headers[\\"Key\\"]).toBe(\\"Set at folder-3-request\\")\\n pw.expect(pw.response.body.headers[\\"Custom-Header-Request-Level\\"]).toBe(\\"New custom header added at the folder-3-request level\\")\\n})","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + ], + requests: [ + { + id: "clx1g2pnv000b10f80f0oyp79", + collectionID: "clx1f86hv000010f8szcfya0t", + teamID: "clws3hg58000011o8h07glsb1", + title: "root-collection-request", + request: `{"v":"${RESTReqSchemaVersion}","auth":{"authType":"inherit","authActive":true},"body":{"body":null,"contentType":null},"name":"root-collection-request","method":"GET","params":[],"headers":[],"endpoint":"https://httpbin.org/get","testScript":"// Check status code is 200\\npw.test(\\"Status code is 200\\", ()=> {\\n pw.expect(pw.response.status).toBe(200);\\n});\\n\\npw.test(\\"Successfully inherits authorization/header set at the parent collection level\\", () => {\\n pw.expect(pw.response.body.headers[\\"Authorization\\"]).toBe(\\"Basic dGVzdHVzZXI6dGVzdHBhc3M=\\")\\n \\n pw.expect(pw.response.body.headers[\\"Custom-Header\\"]).toBe(\\"Custom header value set at the root collection\\")\\n pw.expect(pw.response.body.headers[\\"Inherited-Header\\"]).toBe(\\"Inherited header at all levels\\")\\n})","preRequestScript":"","requestVariables":[],"responses":{}}`, + }, + ], + }, + ]; + +export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: HoppCollection[] = + [ + { + v: 5, + id: "clx1f86hv000010f8szcfya0t", + name: "Multiple child collections with authorization & headers set at each level", + folders: [ + { + v: 5, + id: "clx1fjgah000110f8a5bs68gd", + name: "folder-1", + folders: [ + { + v: 5, + id: "clx1fjwmm000410f8l1gkkr1a", + name: "folder-11", + folders: [], + requests: [ + { + v: "4", + auth: { + authType: "inherit", + password: "testpass", + username: "testuser", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-11-request", + method: "GET", + params: [], + headers: [], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits authorization/header set at the parent collection level", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe("Basic dGVzdHVzZXI6dGVzdHBhc3M=")\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-1")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "inherit", + authActive: true, + }, + headers: [ + { + key: "key", + value: "Set at folder-11", + active: true, + description: "", + }, + ], + }, + { + v: 5, + id: "clx1fjyxm000510f8pv90dt43", + name: "folder-12", + folders: [], + requests: [ + { + v: "4", + auth: { + authType: "none", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-12-request", + method: "GET", + params: [], + headers: [ + { + key: "Custom-Header", + value: + "Custom header value overriden at folder-12-request", + active: true, + }, + { + key: "key", + value: "Overriden at folder-12-request", + active: true, + }, + ], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits/overrides authorization/header set at the parent collection level", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe(undefined)\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-12-request")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n pw.expect(pw.response.body.headers["Key"]).toBe("Overriden at folder-12-request")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "none", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Custom header value overriden at folder-12", + active: true, + description: "", + }, + { + key: "key", + value: "Set at folder-12", + active: true, + description: "", + }, + ], + }, + { + v: 5, + id: "clx1fk1cv000610f88kc3aupy", + name: "folder-13", + folders: [], + requests: [ + { + v: "4", + auth: { + key: "api-key", + addTo: "HEADERS", + value: "api-key-value", + authType: "basic", + password: "testpass", + username: "testuser", + authActive: true, + grantTypeInfo: { + token: "", + isPKCE: true, + clientID: "sfasfa", + password: "", + username: "", + grantType: "AUTHORIZATION_CODE", + authEndpoint: "asfafs", + clientSecret: "sfasfasf", + tokenEndpoint: "asfa", + codeVerifierMethod: "S256", + }, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-13-request", + method: "GET", + params: [], + headers: [ + { + key: "Custom-Header-Request-Level", + value: + "New custom header added at the folder-13-request level", + active: true, + }, + { + key: "key", + value: "Overriden at folder-13-request", + active: true, + }, + ], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits/overrides authorization/header set at the parent collection level with new header addition", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe("Basic dGVzdHVzZXI6dGVzdHBhc3M=")\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-13")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n pw.expect(pw.response.body.headers["Key"]).toBe("Overriden at folder-13-request")\n pw.expect(pw.response.body.headers["Custom-Header-Request-Level"]).toBe("New custom header added at the folder-13-request level")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + token: "test-token", + authType: "bearer", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Custom header value overriden at folder-13", + active: true, + description: "", + }, + { + key: "key", + value: "Set at folder-13", + active: true, + description: "", + }, + ], + }, + ], + requests: [ + { + v: "4", + auth: { + authType: "inherit", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-1-request", + method: "GET", + params: [], + headers: [], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits authorization/header set at the parent collection level", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe("Basic dGVzdHVzZXI6dGVzdHBhc3M=")\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-1")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "inherit", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Custom header value overriden at folder-1", + active: true, + description: "", + }, + ], + }, + { + v: 5, + id: "clx1fjk9o000210f8j0573pls", + name: "folder-2", + folders: [ + { + v: 5, + id: "clx1fk516000710f87sfpw6bo", + name: "folder-21", + folders: [], + requests: [ + { + v: "4", + auth: { + authType: "inherit", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-21-request", + method: "GET", + params: [], + headers: [], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits authorization/header set at the parent collection level", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe(undefined)\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-2")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "inherit", + authActive: true, + }, + headers: [ + { + key: "key", + value: "Set at folder-21", + active: true, + description: "", + }, + ], + }, + { + v: 5, + id: "clx1fk72t000810f8gfwkpi5y", + name: "folder-22", + folders: [], + requests: [ + { + v: "4", + auth: { + authType: "none", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-22-request", + method: "GET", + params: [], + headers: [ + { + key: "Custom-Header", + value: + "Custom header value overriden at folder-22-request", + active: true, + }, + { + key: "key", + value: "Overriden at folder-22-request", + active: true, + }, + ], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits/overrides authorization/header set at the parent collection level", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe(undefined)\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-22-request")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n pw.expect(pw.response.body.headers["Key"]).toBe("Overriden at folder-22-request")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "none", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Custom header value overriden at folder-22", + active: true, + description: "", + }, + { + key: "key", + value: "Set at folder-22", + active: true, + description: "", + }, + ], + }, + { + v: 5, + id: "clx1fk95g000910f8bunhaoo8", + name: "folder-23", + folders: [], + requests: [ + { + v: "4", + auth: { + authType: "basic", + password: "testpass", + username: "testuser", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-23-request", + method: "GET", + params: [], + headers: [ + { + key: "Custom-Header-Request-Level", + value: + "New custom header added at the folder-23-request level", + active: true, + }, + { + key: "key", + value: "Overriden at folder-23-request", + active: true, + }, + ], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits/overrides authorization/header set at the parent collection level with new header addition", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe("Basic dGVzdHVzZXI6dGVzdHBhc3M=")\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-23")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n pw.expect(pw.response.body.headers["Key"]).toBe("Overriden at folder-23-request")\n pw.expect(pw.response.body.headers["Custom-Header-Request-Level"]).toBe("New custom header added at the folder-23-request level")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + token: "test-token", + authType: "bearer", + password: "testpass", + username: "testuser", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Custom header value overriden at folder-23", + active: true, + description: "", + }, + { + key: "key", + value: "Set at folder-23", + active: true, + description: "", + }, + ], + }, + ], + requests: [ + { + v: "4", + auth: { + authType: "none", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-2-request", + method: "GET", + params: [], + headers: [ + { + key: "Custom-Header", + value: "Custom header value overriden at folder-2-request", + active: true, + }, + ], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits/overrides authorization/header set at the parent collection level", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe(undefined)\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-2-request")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "none", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Custom header value overriden at folder-2", + active: true, + description: "", + }, + ], + }, + { + v: 5, + id: "clx1fjmlq000310f86o4d3w2o", + name: "folder-3", + folders: [ + { + v: 5, + id: "clx1iwq0p003e10f8u8zg0p85", + name: "folder-31", + folders: [], + requests: [ + { + v: "4", + auth: { + authType: "inherit", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-31-request", + method: "GET", + params: [], + headers: [], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits authorization/header set at the parent collection level", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe("Basic dGVzdHVzZXI6dGVzdHBhc3M=")\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-3")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "inherit", + authActive: true, + }, + headers: [ + { + key: "key", + value: "Set at folder-31", + active: true, + description: "", + }, + ], + }, + { + v: 5, + id: "clx1izut7003m10f894ip59zg", + name: "folder-32", + folders: [], + requests: [ + { + v: "4", + auth: { + authType: "none", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-32-request", + method: "GET", + params: [], + headers: [ + { + key: "Custom-Header", + value: + "Custom header value overriden at folder-32-request", + active: true, + }, + { + key: "key", + value: "Overriden at folder-32-request", + active: true, + }, + ], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits/overrides authorization/header set at the parent collection level", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe(undefined)\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-32-request")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n pw.expect(pw.response.body.headers["Key"]).toBe("Overriden at folder-32-request")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "none", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Custom header value overriden at folder-32", + active: true, + description: "", + }, + { + key: "key", + value: "Set at folder-32", + active: true, + description: "", + }, + ], + }, + { + v: 5, + id: "clx1j2ka9003q10f8cdbzpgpg", + name: "folder-33", + folders: [], + requests: [ + { + v: "4", + auth: { + authType: "basic", + password: "testpass", + username: "testuser", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-33-request", + method: "GET", + params: [], + headers: [ + { + key: "Custom-Header-Request-Level", + value: + "New custom header added at the folder-33-request level", + active: true, + }, + { + key: "key", + value: "Overriden at folder-33-request", + active: true, + }, + ], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits/overrides authorization/header set at the parent collection level with new header addition", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe("Basic dGVzdHVzZXI6dGVzdHBhc3M=")\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-33")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n pw.expect(pw.response.body.headers["Key"]).toBe("Overriden at folder-33-request")\n pw.expect(pw.response.body.headers["Custom-Header-Request-Level"]).toBe("New custom header added at the folder-33-request level")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + token: "test-token", + authType: "bearer", + password: "testpass", + username: "testuser", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Custom header value overriden at folder-33", + active: true, + description: "", + }, + { + key: "key", + value: "Set at folder-33", + active: true, + description: "", + }, + ], + }, + ], + requests: [ + { + v: "4", + auth: { + authType: "basic", + password: "testpass", + username: "testuser", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "folder-3-request", + method: "GET", + params: [], + headers: [ + { + key: "Custom-Header-Request-Level", + value: + "New custom header added at the folder-3-request level", + active: true, + }, + { + key: "key", + value: "Set at folder-3-request", + active: true, + }, + ], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits/overrides authorization/header set at the parent collection level with new header addition", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe("Basic dGVzdHVzZXI6dGVzdHBhc3M=")\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value overriden at folder-3")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n pw.expect(pw.response.body.headers["Key"]).toBe("Set at folder-3-request")\n pw.expect(pw.response.body.headers["Custom-Header-Request-Level"]).toBe("New custom header added at the folder-3-request level")\n})', + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + key: "testuser", + addTo: "HEADERS", + value: "testpass", + authType: "basic", + password: "testpass", + username: "testuser", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Custom header value overriden at folder-3", + active: true, + description: "", + }, + ], + }, + ], + requests: [ + { + v: RESTReqSchemaVersion, + auth: { + authType: "inherit", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "root-collection-request", + method: "GET", + params: [], + headers: [], + endpoint: "https://httpbin.org/get", + testScript: + '// Check status code is 200\npw.test("Status code is 200", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\npw.test("Successfully inherits authorization/header set at the parent collection level", () => {\n pw.expect(pw.response.body.headers["Authorization"]).toBe("Basic dGVzdHVzZXI6dGVzdHBhc3M=")\n \n pw.expect(pw.response.body.headers["Custom-Header"]).toBe("Custom header value set at the root collection")\n pw.expect(pw.response.body.headers["Inherited-Header"]).toBe("Inherited header at all levels")\n})', + preRequestScript: "", + requestVariables: [], + responses: {}, + }, + ], + auth: { + authType: "basic", + password: "testpass", + username: "testuser", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Custom header value set at the root collection", + active: true, + description: "", + }, + { + key: "Inherited-Header", + value: "Inherited header at all levels", + active: true, + description: "", + }, + ], + }, + ]; + +// Collections with `data` field set to `null` at certain levels +export const WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK: WorkspaceCollection[] = + [ + { + id: "clx1kxvao005m10f8luqivrf1", + data: null, + title: "Collection with no authorization/headers set", + parentID: null, + folders: [ + { + id: "clx1kygjt005n10f8m1nkhjux", + data: null, + title: "folder-1", + parentID: "clx1kxvao005m10f8luqivrf1", + folders: [], + requests: [ + { + id: "clx1kz2gk005p10f8ll7ztbnj", + collectionID: "clx1kygjt005n10f8m1nkhjux", + teamID: "clws3hg58000011o8h07glsb1", + title: "req1", + request: + '{"v":"4","auth":{"authType":"inherit","authActive":true},"body":{"body":null,"contentType":null},"name":"req1","method":"GET","params":[],"headers":[],"endpoint":"https://echo.hoppscotch.io","testScript":"","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + { + id: "clx1kym98005o10f8qg17t9o2", + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Set at folder-2","active":true}]}', + title: "folder-2", + parentID: "clx1kxvao005m10f8luqivrf1", + folders: [], + requests: [ + { + id: "clx1kz3m7005q10f8lw3v09l4", + collectionID: "clx1kym98005o10f8qg17t9o2", + teamID: "clws3hg58000011o8h07glsb1", + title: "req2", + request: + '{"v":"4","auth":{"authType":"inherit","authActive":true},"body":{"body":null,"contentType":null},"name":"req2","method":"GET","params":[],"headers":[],"endpoint":"https://echo.hoppscotch.io","testScript":"","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + { + id: "clx1l2bu6005r10f8daynohge", + data: null, + title: "folder-3", + parentID: "clx1kxvao005m10f8luqivrf1", + folders: [], + requests: [], + }, + { + id: "clx1l2eaz005s10f8loetbbeb", + data: '{"auth":{"authType":"none","authActive":true},"headers":[{"key":"Custom-Header","value":"Set at folder-4","active":true}]}', + title: "folder-4", + parentID: "clx1kxvao005m10f8luqivrf1", + folders: [], + requests: [], + }, + ], + requests: [], + }, + ]; + +export const TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK: HoppCollection[] = + [ + { + v: CollectionSchemaVersion, + id: "clx1kxvao005m10f8luqivrf1", + name: "Collection with no authorization/headers set", + folders: [ + { + v: CollectionSchemaVersion, + id: "clx1kygjt005n10f8m1nkhjux", + name: "folder-1", + folders: [], + requests: [ + { + v: "4", + auth: { + authType: "inherit", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "req1", + method: "GET", + params: [], + headers: [], + endpoint: "https://echo.hoppscotch.io", + testScript: "", + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "inherit", + authActive: true, + }, + headers: [], + }, + { + v: CollectionSchemaVersion, + id: "clx1kym98005o10f8qg17t9o2", + name: "folder-2", + folders: [], + requests: [ + { + v: "4", + auth: { + authType: "inherit", + authActive: true, + }, + body: { + body: null, + contentType: null, + }, + name: "req2", + method: "GET", + params: [], + headers: [], + endpoint: "https://echo.hoppscotch.io", + testScript: "", + preRequestScript: "", + requestVariables: [], + }, + ], + auth: { + authType: "none", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Set at folder-2", + active: true, + description: "", + }, + ], + }, + { + v: CollectionSchemaVersion, + id: "clx1l2bu6005r10f8daynohge", + name: "folder-3", + folders: [], + requests: [], + auth: { + authType: "inherit", + authActive: true, + }, + headers: [], + }, + { + v: CollectionSchemaVersion, + id: "clx1l2eaz005s10f8loetbbeb", + name: "folder-4", + folders: [], + requests: [], + auth: { + authType: "none", + authActive: true, + }, + headers: [ + { + key: "Custom-Header", + value: "Set at folder-4", + active: true, + description: "", + }, + ], + }, + ], + requests: [], + auth: { + authType: "inherit", + authActive: true, + }, + headers: [], + }, + ]; + +export const WORKSPACE_ENVIRONMENT_MOCK: WorkspaceEnvironment = { + id: "clwudd68q00079rufju8uo3on", + teamID: "clws3hg58000011o8h07glsb1", + name: "Response body sample", + variables: [ + { + key: "firstName", + value: "John", + secret: false, + }, + { + key: "lastName", + value: "Doe", + secret: false, + }, + { + key: "id", + value: "7", + secret: false, + }, + { + key: "fullName", + value: "<> <>", + secret: false, + }, + { + key: "recursiveVarX", + value: "<>", + secret: false, + }, + { + key: "recursiveVarY", + value: "<>", + secret: false, + }, + { + key: "salutation", + value: "Hello", + secret: false, + }, + { + key: "greetText", + value: "<> <>", + secret: false, + }, + ], +}; + +export const TRANSFORMED_ENVIRONMENT_MOCK: Environment = { + v: EnvironmentSchemaVersion, + id: "clwudd68q00079rufju8uo3on", + name: "Response body sample", + variables: [ + { + key: "firstName", + value: "John", + secret: false, + }, + { + key: "lastName", + value: "Doe", + secret: false, + }, + { + key: "id", + value: "7", + secret: false, + }, + { + key: "fullName", + value: "<> <>", + secret: false, + }, + { + key: "recursiveVarX", + value: "<>", + secret: false, + }, + { + key: "recursiveVarY", + value: "<>", + secret: false, + }, + { + key: "salutation", + value: "Hello", + secret: false, + }, + { + key: "greetText", + value: "<> <>", + secret: false, + }, + ], +}; diff --git a/packages/hoppscotch-cli/src/__tests__/unit/getters.spec.ts b/packages/hoppscotch-cli/src/__tests__/unit/getters.spec.ts new file mode 100644 index 0000000000..3950a9fe62 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/unit/getters.spec.ts @@ -0,0 +1,488 @@ +import axios, { AxiosError, AxiosResponse } from "axios"; +import fs from "fs/promises"; +import { describe, expect, test, vi } from "vitest"; + +import { + CollectionSchemaVersion, + HoppCollection, + getDefaultRESTRequest, +} from "@hoppscotch/data"; + +import { DEFAULT_DURATION_PRECISION } from "../../utils/constants"; +import { + getDurationInSeconds, + getEffectiveFinalMetaData, + getResolvedVariables, + getResourceContents, +} from "../../utils/getters"; +import * as mutators from "../../utils/mutators"; + +import * as workspaceAccessHelpers from "../../utils/workspace-access"; + +describe("getters", () => { + describe("getDurationInSeconds", () => { + const testDurations = [ + { end: [1, 111111111], precision: 1, expected: 1.1 }, + { end: [2, 333333333], precision: 2, expected: 2.33 }, + { + end: [3, 555555555], + precision: DEFAULT_DURATION_PRECISION, + expected: 3.556, + }, + { end: [4, 777777777], precision: 4, expected: 4.7778 }, + ]; + + test.each(testDurations)( + "($end.0 s + $end.1 ns) rounded-off to $expected", + ({ end, precision, expected }) => { + expect(getDurationInSeconds(end as [number, number], precision)).toBe( + expected + ); + } + ); + }); + + describe("getEffectiveFinalMetaData", () => { + const environmentVariables = [ + { key: "PARAM", value: "parsed_param", secret: false }, + ]; + + test("Empty list of meta-data", () => { + expect( + getEffectiveFinalMetaData([], environmentVariables) + ).toSubsetEqualRight([]); + }); + + test("Non-empty active list of meta-data with unavailable ENV", () => { + expect( + getEffectiveFinalMetaData( + [ + { + active: true, + key: "<>", + value: "<>", + description: "", + }, + ], + environmentVariables + ) + ).toSubsetEqualRight([{ active: true, key: "", value: "" }]); + }); + + test("Inactive list of meta-data", () => { + expect( + getEffectiveFinalMetaData( + [{ active: false, key: "KEY", value: "<>", description: "" }], + environmentVariables + ) + ).toSubsetEqualRight([]); + }); + + test("Active list of meta-data", () => { + expect( + getEffectiveFinalMetaData( + [{ active: true, key: "PARAM", value: "<>", description: "" }], + environmentVariables + ) + ).toSubsetEqualRight([ + { active: true, key: "PARAM", value: "parsed_param" }, + ]); + }); + }); + + describe("getResourceContents", () => { + describe("Network call failure", () => { + const args = { + pathOrId: "test-collection-id-or-path", + resourceType: "collection" as const, + accessToken: "test-token", + serverUrl: "test-url", + }; + + const cases = [ + { + description: + "Promise rejects with the code `SERVER_CONNECTION_REFUSED` if the network call fails with the code `ECONNREFUSED`", + args, + axiosMock: { + code: "ECONNREFUSED", + }, + expected: { + code: "SERVER_CONNECTION_REFUSED", + data: args.serverUrl, + }, + }, + { + description: + "Promise rejects with the code `INVALID_SERVER_URL` if the network call fails with the code `ERR_INVALID_URL`", + args, + axiosMock: { + code: "ERR_INVALID_URL", + }, + expected: { + code: "INVALID_SERVER_URL", + data: args.serverUrl, + }, + }, + { + description: + "Promise rejects with the code `INVALID_SERVER_URL` if the network call fails with the code `ENOTFOUND`", + args, + axiosMock: { + code: "ENOTFOUND", + }, + expected: { + code: "INVALID_SERVER_URL", + data: args.serverUrl, + }, + }, + { + description: + "Promise rejects with the code `INVALID_SERVER_URL` if the network call returns a response with a status code of `404`", + args, + axiosMock: { + response: { + status: 404, + }, + }, + expected: { + code: "INVALID_SERVER_URL", + data: args.serverUrl, + }, + }, + { + description: + "Promise rejects with the code `TOKEN_EXPIRED` if the network call fails for the same reason", + args, + axiosMock: { + response: { + data: { + reason: "TOKEN_EXPIRED", + }, + }, + }, + expected: { + code: "TOKEN_EXPIRED", + data: args.accessToken, + }, + }, + { + description: + "Promise rejects with the code `TOKEN_INVALID` if the network call fails for the same reason", + args, + axiosMock: { + response: { + data: { + reason: "TOKEN_INVALID", + }, + }, + }, + expected: { + code: "TOKEN_INVALID", + data: args.accessToken, + }, + }, + { + description: + "Promise rejects with the code `INVALID_ID` if the network call fails for the same reason when the supplied collection ID or path is invalid", + args, + axiosMock: { + response: { + data: { + reason: "INVALID_ID", + }, + }, + }, + expected: { + code: "INVALID_ID", + data: args.pathOrId, + }, + }, + { + description: + "Promise rejects with the code `INVALID_ID` if the network call fails for the same reason when the supplied environment ID or path is invalid", + args: { + ...args, + pathOrId: "test-environment-id-or-path", + resourceType: "environment" as const, + }, + axiosMock: { + response: { + data: { + reason: "INVALID_ID", + }, + }, + }, + expected: { + code: "INVALID_ID", + data: "test-environment-id-or-path", + }, + }, + ]; + + test.each(cases)("$description", ({ args, axiosMock, expected }) => { + const { code, response } = axiosMock; + const axiosErrMessage = code ?? response?.data?.reason; + + vi.spyOn(axios, "get").mockImplementation(() => + Promise.reject( + new AxiosError( + axiosErrMessage, + code, + undefined, + undefined, + response as AxiosResponse + ) + ) + ); + + expect(getResourceContents(args)).rejects.toEqual(expected); + }); + + test("Promise rejects with the code `INVALID_SERVER_URL` if the network call succeeds and the received response content type is not `application/json`", () => { + const expected = { + code: "INVALID_SERVER_URL", + data: args.serverUrl, + }; + + vi.spyOn(axios, "get").mockImplementation(() => + Promise.resolve({ + data: "", + headers: { "content-type": "text/html; charset=UTF-8" }, + }) + ); + + expect(getResourceContents(args)).rejects.toEqual(expected); + }); + + test("Promise rejects with the code `UNKNOWN_ERROR` while encountering an error that is not an instance of `AxiosError`", () => { + const expected = { + code: "UNKNOWN_ERROR", + data: new TypeError("UNKNOWN_ERROR"), + }; + + vi.spyOn(axios, "get").mockImplementation(() => + Promise.reject(new Error("UNKNOWN_ERROR")) + ); + + expect(getResourceContents(args)).rejects.toEqual(expected); + }); + }); + + describe("Success", () => { + test("Proceeds with reading from the file system if the supplied file exists in the path", async () => { + fs.access = vi.fn().mockResolvedValueOnce(undefined); + + const sampleCollectionContents: HoppCollection = { + v: CollectionSchemaVersion, + id: "valid-collection-id", + name: "valid-collection-title", + folders: [], + requests: [], + headers: [], + auth: { + authType: "none", + authActive: false, + }, + }; + + axios.get = vi.fn(); + + vi.spyOn(mutators, "readJsonFile").mockImplementation(() => + Promise.resolve(sampleCollectionContents) + ); + + const pathOrId = "valid-collection-file-path"; + const resourceType = "collection"; + const accessToken = "valid-access-token"; + const serverUrl = "valid-url"; + + const contents = await getResourceContents({ + pathOrId, + accessToken, + serverUrl, + resourceType, + }); + + expect(fs.access).toHaveBeenCalledWith(pathOrId); + expect(axios.get).not.toBeCalled(); + expect(mutators.readJsonFile).toHaveBeenCalledWith(pathOrId, true); + + expect(contents).toEqual(sampleCollectionContents); + }); + + test("Proceeds with the network call if a value for the access token is specified and the supplied path/id is not a valid file path", async () => { + fs.access = vi.fn().mockRejectedValueOnce(undefined); + + const sampleCollectionContents: HoppCollection = { + v: CollectionSchemaVersion, + name: "test-coll", + folders: [], + requests: [getDefaultRESTRequest()], + headers: [], + auth: { + authType: "none", + authActive: false, + }, + }; + + axios.get = vi.fn().mockImplementation(() => + Promise.resolve({ + data: { + id: "clx06ik0o00028t6uwywwnxgg", + data: null, + title: "test-coll", + parentID: null, + folders: [], + requests: [ + { + id: "clx06imin00038t6uynt5vyk4", + collectionID: "clx06ik0o00028t6uwywwnxgg", + teamID: "clwt6r6j10031kc6pu0b08y6e", + title: "req1", + request: + '{"v":"4","auth":{"authType":"inherit","authActive":true},"body":{"body":null,"contentType":null},"name":"req1","method":"GET","params":[],"headers":[],"endpoint":"https://echo.hoppscotch.io","testScript":"","preRequestScript":"","requestVariables":[]}', + }, + ], + }, + headers: { + "content-type": "application/json", + }, + }) + ); + + vi.spyOn(mutators, "readJsonFile").mockImplementation(() => + Promise.resolve(sampleCollectionContents) + ); + + vi.spyOn( + workspaceAccessHelpers, + "transformWorkspaceCollections" + ).mockImplementation(() => [sampleCollectionContents]); + + const pathOrId = "valid-collection-id"; + const resourceType = "collection"; + const accessToken = "valid-access-token"; + const serverUrl = "valid-url"; + + await getResourceContents({ + pathOrId, + accessToken, + serverUrl, + resourceType, + }); + + expect(fs.access).toHaveBeenCalledWith(pathOrId); + expect(axios.get).toBeCalledWith( + `${serverUrl}/v1/access-tokens/${resourceType}/${pathOrId}`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + } + ); + expect( + workspaceAccessHelpers.transformWorkspaceCollections + ).toBeCalled(); + expect(mutators.readJsonFile).not.toHaveBeenCalled(); + }); + }); + }); + + describe("getResolvedVariables", () => { + const requestVariables = [ + { + key: "SHARED_KEY_I", + value: "request-variable-shared-value-I", + active: true, + }, + { + key: "SHARED_KEY_II", + value: "", + active: true, + }, + { + key: "REQUEST_VAR_III", + value: "request-variable-value-III", + active: true, + }, + { + key: "REQUEST_VAR_IV", + value: "request-variable-value-IV", + active: false, + }, + { + key: "REQUEST_VAR_V", + value: "request-variable-value-V", + active: false, + }, + ]; + + const environmentVariables = [ + { + key: "SHARED_KEY_I", + value: "environment-variable-shared-value-I", + secret: false, + }, + { + key: "SHARED_KEY_II", + value: "environment-variable-shared-value-II", + secret: false, + }, + { + key: "ENV_VAR_III", + value: "environment-variable-value-III", + secret: false, + }, + { + key: "ENV_VAR_IV", + value: "environment-variable-value-IV", + secret: false, + }, + { + key: "ENV_VAR_V", + value: "environment-variable-value-V", + secret: false, + }, + ]; + + test("Filters request variables by active status and value fields, then remove environment variables sharing the same keys", () => { + const expected = [ + { + key: "SHARED_KEY_I", + value: "request-variable-shared-value-I", + secret: false, + }, + { + key: "REQUEST_VAR_III", + value: "request-variable-value-III", + secret: false, + }, + { + key: "SHARED_KEY_II", + value: "environment-variable-shared-value-II", + secret: false, + }, + { + key: "ENV_VAR_III", + value: "environment-variable-value-III", + secret: false, + }, + { + key: "ENV_VAR_IV", + value: "environment-variable-value-IV", + secret: false, + }, + { + key: "ENV_VAR_V", + value: "environment-variable-value-V", + secret: false, + }, + ]; + + expect( + getResolvedVariables(requestVariables, environmentVariables) + ).toEqual(expected); + }); + }); +}); diff --git a/packages/hoppscotch-cli/src/__tests__/unit/workspace-access.spec.ts b/packages/hoppscotch-cli/src/__tests__/unit/workspace-access.spec.ts new file mode 100644 index 0000000000..51e5dbf2ed --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/unit/workspace-access.spec.ts @@ -0,0 +1,54 @@ +import { describe, expect, test } from "vitest"; + +import { + transformWorkspaceCollections, + transformWorkspaceEnvironment, +} from "../../utils/workspace-access"; +import { + TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK, + TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK, + TRANSFORMED_ENVIRONMENT_MOCK, + TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK, + WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK, + WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK, + WORKSPACE_ENVIRONMENT_MOCK, + WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK, +} from "./fixtures/workspace-access.mock"; + +describe("workspace-access", () => { + describe("transformWorkspaceCollection", () => { + test("Successfully transforms collection data with deeply nested collections and authorization/headers set at each level to the `HoppCollection` format", () => { + expect( + transformWorkspaceCollections( + WORKSPACE_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK + ) + ).toEqual(TRANSFORMED_DEEPLY_NESTED_COLLECTIONS_WITH_AUTH_HEADERS_MOCK); + }); + + test("Successfully transforms collection data with multiple child collections and authorization/headers set at each level to the `HoppCollection` format", () => { + expect( + transformWorkspaceCollections( + WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK + ) + ).toEqual(TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK); + }); + + test("Adds the default value for `auth` & `header` fields while transforming collections without authorization/headers set at certain levels", () => { + expect( + transformWorkspaceCollections( + WORKSPACE_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK + ) + ).toEqual( + TRANSFORMED_COLLECTIONS_WITHOUT_AUTH_HEADERS_AT_CERTAIN_LEVELS_MOCK + ); + }); + }); + + describe("transformWorkspaceEnvironment", () => { + test("Successfully transforms environment data conforming to the format received from the network call to the `HoppEnvironment` format", () => { + expect(transformWorkspaceEnvironment(WORKSPACE_ENVIRONMENT_MOCK)).toEqual( + TRANSFORMED_ENVIRONMENT_MOCK + ); + }); + }); +}); diff --git a/packages/hoppscotch-cli/src/__tests__/utils.ts b/packages/hoppscotch-cli/src/__tests__/utils.ts index f6068b2d69..c86a655dca 100644 --- a/packages/hoppscotch-cli/src/__tests__/utils.ts +++ b/packages/hoppscotch-cli/src/__tests__/utils.ts @@ -3,15 +3,16 @@ import { resolve } from "path"; import { ExecResponse } from "./types"; -export const runCLI = (args: string, options = {}): Promise => - { - const CLI_PATH = resolve(__dirname, "../../bin/hopp"); - const command = `node ${CLI_PATH} ${args}` - - return new Promise((resolve) => - exec(command, options, (error, stdout, stderr) => resolve({ error, stdout, stderr })) - ); - } +export const runCLI = (args: string, options = {}): Promise => { + const CLI_PATH = resolve(__dirname, "../../bin/hopp.js"); + const command = `node ${CLI_PATH} ${args}`; + + return new Promise((resolve) => + exec(command, options, (error, stdout, stderr) => + resolve({ error, stdout, stderr }) + ) + ); +}; export const trimAnsi = (target: string) => { const ansiRegex = @@ -25,12 +26,18 @@ export const getErrorCode = (out: string) => { return ansiTrimmedStr.split(" ")[0]; }; -export const getTestJsonFilePath = (file: string, kind: "collection" | "environment") => { +export const getTestJsonFilePath = ( + file: string, + kind: "collection" | "environment" +) => { const kindDir = { collection: "collections", environment: "environments", }[kind]; - const filePath = resolve(__dirname, `../../src/__tests__/samples/${kindDir}/${file}`); + const filePath = resolve( + __dirname, + `../../src/__tests__/e2e/fixtures/${kindDir}/${file}` + ); return filePath; }; diff --git a/packages/hoppscotch-cli/src/commands/test.ts b/packages/hoppscotch-cli/src/commands/test.ts index bdc5638923..0291fa31f3 100644 --- a/packages/hoppscotch-cli/src/commands/test.ts +++ b/packages/hoppscotch-cli/src/commands/test.ts @@ -1,30 +1,104 @@ +import fs from "fs"; +import { isSafeInteger } from "lodash-es"; +import Papa from "papaparse"; +import path from "path"; + +import { handleError } from "../handlers/error"; +import { parseDelayOption } from "../options/test/delay"; +import { parseEnvsData } from "../options/test/env"; +import { IterationDataItem } from "../types/collections"; +import { TestCmdEnvironmentOptions, TestCmdOptions } from "../types/commands"; +import { error } from "../types/errors"; +import { HoppEnvs } from "../types/request"; +import { isHoppCLIError } from "../utils/checks"; import { collectionsRunner, collectionsRunnerExit, collectionsRunnerResult, } from "../utils/collections"; -import { handleError } from "../handlers/error"; import { parseCollectionData } from "../utils/mutators"; -import { parseEnvsData } from "../options/test/env"; -import { TestCmdOptions } from "../types/commands"; -import { parseDelayOption } from "../options/test/delay"; -import { HoppEnvs } from "../types/request"; -import { isHoppCLIError } from "../utils/checks"; -export const test = (path: string, options: TestCmdOptions) => async () => { +export const test = (pathOrId: string, options: TestCmdOptions) => async () => { try { - const delay = options.delay ? parseDelayOption(options.delay) : 0 - const envs = options.env ? await parseEnvsData(options.env) : { global: [], selected: [] } - const collections = await parseCollectionData(path) - - const report = await collectionsRunner({collections, envs, delay}) - const hasSucceeded = collectionsRunnerResult(report) - collectionsRunnerExit(hasSucceeded) - } catch(e) { - if(isHoppCLIError(e)) { - handleError(e) - process.exit(1); + const { delay, env, iterationCount, iterationData, reporterJunit } = + options; + + if ( + iterationCount !== undefined && + (iterationCount < 1 || !isSafeInteger(iterationCount)) + ) { + throw error({ + code: "INVALID_ARGUMENT", + data: "The value must be a positive integer", + }); } - else throw e + + const resolvedDelay = delay ? parseDelayOption(delay) : 0; + + const envs = env + ? await parseEnvsData(options as TestCmdEnvironmentOptions) + : { global: [], selected: [] }; + + let parsedIterationData: unknown[] | null = null; + let transformedIterationData: IterationDataItem[][] | undefined; + + const collections = await parseCollectionData(pathOrId, options); + + if (iterationData) { + // Check file existence + if (!fs.existsSync(iterationData)) { + throw error({ code: "FILE_NOT_FOUND", path: iterationData }); + } + + // Check the file extension + if (path.extname(iterationData) !== ".csv") { + throw error({ + code: "INVALID_DATA_FILE_TYPE", + data: iterationData, + }); + } + + const csvData = fs.readFileSync(iterationData, "utf8"); + parsedIterationData = Papa.parse(csvData, { header: true }).data; + + // Transform data into the desired format + transformedIterationData = parsedIterationData + .map((item) => { + const iterationDataItem = item as Record; + const keys = Object.keys(iterationDataItem); + + return ( + keys + // Ignore keys with empty string values + .filter((key) => iterationDataItem[key] !== "") + .map( + (key) => + { + key: key, + value: iterationDataItem[key], + secret: false, + } + ) + ); + }) + // Ignore items that result in an empty array + .filter((item) => item.length > 0); + } + + const report = await collectionsRunner({ + collections, + envs, + delay: resolvedDelay, + iterationData: transformedIterationData, + iterationCount, + }); + const hasSucceeded = collectionsRunnerResult(report, reporterJunit); + + collectionsRunnerExit(hasSucceeded); + } catch (e) { + if (isHoppCLIError(e)) { + handleError(e); + process.exit(1); + } else throw e; } }; diff --git a/packages/hoppscotch-cli/src/handlers/error.ts b/packages/hoppscotch-cli/src/handlers/error.ts index 34430cf48e..72be3accf5 100644 --- a/packages/hoppscotch-cli/src/handlers/error.ts +++ b/packages/hoppscotch-cli/src/handlers/error.ts @@ -63,7 +63,10 @@ export const handleError = (error: HoppError) => { ERROR_MSG = `Unable to parse -\n${error.data}`; break; case "INVALID_FILE_TYPE": - ERROR_MSG = `Please provide file of extension type: ${error.data}`; + ERROR_MSG = `Please provide file of extension type .json: ${error.data}`; + break; + case "INVALID_DATA_FILE_TYPE": + ERROR_MSG = `Please provide file of extension type .csv: ${error.data}`; break; case "REQUEST_ERROR": case "TEST_SCRIPT_ERROR": @@ -82,6 +85,25 @@ export const handleError = (error: HoppError) => { case "TESTS_FAILING": ERROR_MSG = error.data; break; + case "TOKEN_EXPIRED": + ERROR_MSG = `The specified access token is expired. Please provide a valid token: ${error.data}`; + break; + case "TOKEN_INVALID": + ERROR_MSG = `The specified access token is invalid. Please provide a valid token: ${error.data}`; + break; + case "INVALID_ID": + ERROR_MSG = `The specified collection/environment (ID or file path) is invalid or inaccessible. Please ensure the supplied ID or file path is correct: ${error.data}`; + break; + case "INVALID_SERVER_URL": + ERROR_MSG = `Please provide a valid SH instance server URL: ${error.data}`; + break; + case "SERVER_CONNECTION_REFUSED": + ERROR_MSG = `Unable to connect to the server. Please check your network connection or server instance URL and try again: ${error.data}`; + break; + case "REPORT_EXPORT_FAILED": + const moreInfo = error.data ? `: ${error.data}` : S.empty; + ERROR_MSG = `Failed to export the report at ${error.path}${moreInfo}`; + break; } if (!S.isEmpty(ERROR_MSG)) { diff --git a/packages/hoppscotch-cli/src/index.ts b/packages/hoppscotch-cli/src/index.ts index bf81a7ab49..7b92a47a01 100644 --- a/packages/hoppscotch-cli/src/index.ts +++ b/packages/hoppscotch-cli/src/index.ts @@ -1,6 +1,7 @@ import chalk from "chalk"; import { Command } from "commander"; import * as E from "fp-ts/Either"; + import { version } from "../package.json"; import { test } from "./commands/test"; import { handleError } from "./handlers/error"; @@ -17,10 +18,10 @@ const CLI_BEFORE_ALL_TXT = `hopp: The ${accent( )}) ${chalk.black.bold.bgYellowBright(" ALPHA ")} \n`; const CLI_AFTER_ALL_TXT = `\nFor more help, head on to ${accent( - "https://docs.hoppscotch.io/documentation/clients/cli" + "https://docs.hoppscotch.io/documentation/clients/cli/overview" )}`; -const program = new Command() +const program = new Command(); program .name("hopp") @@ -48,24 +49,56 @@ program.exitOverride().configureOutput({ program .command("test") .argument( - "", - "path to a hoppscotch collection.json file for CI testing" + "", + "path to a hoppscotch collection.json file or collection ID from a workspace for CI testing" + ) + .option( + "-e, --env ", + "path to an environment variables json file or environment ID from a workspace" ) - .option("-e, --env ", "path to an environment variables json file") .option( "-d, --delay ", "delay in milliseconds(ms) between consecutive requests within a collection" ) + .option( + "--token ", + "personal access token to access collections/environments from a workspace" + ) + .option("--server ", "server URL for SH instance") + .option( + "--reporter-junit [path]", + "generate JUnit report optionally specifying the path" + ) + .option( + "--iteration-count ", + "number of iterations to run the test", + parseInt + ) + .option( + "--iteration-data ", + "path to a CSV file for data-driven testing" + ) .allowExcessArguments(false) .allowUnknownOption(false) .description("running hoppscotch collection.json file") .addHelpText( "after", `\nFor help, head on to ${accent( - "https://docs.hoppscotch.io/documentation/clients/cli#commands" + "https://docs.hoppscotch.io/documentation/clients/cli/overview#commands" )}` ) - .action(async (path, options) => await test(path, options)()); + .action(async (pathOrId, options) => { + const overrides: Record = {}; + + // Choose `hopp-junit-report.xml` as the default value if `reporter-junit` flag is supplied without a value + if (options.reporterJunit === true) { + overrides.reporterJunit = "hopp-junit-report.xml"; + } + + const effectiveOptions = { ...options, ...overrides }; + + await test(pathOrId, effectiveOptions)(); + }); export const cli = async (args: string[]) => { try { diff --git a/packages/hoppscotch-cli/src/interfaces/request.ts b/packages/hoppscotch-cli/src/interfaces/request.ts index 9eb3982f73..4aec5cbc69 100644 --- a/packages/hoppscotch-cli/src/interfaces/request.ts +++ b/packages/hoppscotch-cli/src/interfaces/request.ts @@ -20,8 +20,7 @@ export interface RequestStack { * @property {boolean} supported - Boolean check for supported or unsupported requests. */ export interface RequestConfig extends AxiosRequestConfig { - supported: boolean; - displayUrl?: string + displayUrl?: string; } export interface EffectiveHoppRESTRequest extends HoppRESTRequest { @@ -32,7 +31,17 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest { */ effectiveFinalURL: string; effectiveFinalDisplayURL?: string; - effectiveFinalHeaders: { key: string; value: string; active: boolean }[]; - effectiveFinalParams: { key: string; value: string; active: boolean }[]; - effectiveFinalBody: FormData | string | null; + effectiveFinalHeaders: { + key: string; + value: string; + active: boolean; + description: string; + }[]; + effectiveFinalParams: { + key: string; + value: string; + active: boolean; + description: string; + }[]; + effectiveFinalBody: FormData | string | File | null; } diff --git a/packages/hoppscotch-cli/src/options/test/env.ts b/packages/hoppscotch-cli/src/options/test/env.ts index 2cec231a1d..cc6746525a 100644 --- a/packages/hoppscotch-cli/src/options/test/env.ts +++ b/packages/hoppscotch-cli/src/options/test/env.ts @@ -1,22 +1,35 @@ -import { Environment } from "@hoppscotch/data"; +import { Environment, NonSecretEnvironment } from "@hoppscotch/data"; import { entityReference } from "verzod"; import { z } from "zod"; +import { TestCmdEnvironmentOptions } from "../../types/commands"; import { error } from "../../types/errors"; import { HoppEnvKeyPairObject, HoppEnvPair, HoppEnvs, } from "../../types/request"; -import { readJsonFile } from "../../utils/mutators"; +import { getResourceContents } from "../../utils/getters"; /** - * Parses env json file for given path and validates the parsed env json object - * @param path Path of env.json file to be parsed - * @returns For successful parsing we get HoppEnvs object + * Parses environment data from a given path or ID and returns the data conforming to the latest version of the `Environment` schema. + * + * @param {TestCmdEnvironmentOptions} options Supplied values for CLI flags. + * @param {string} options.env Path of the environment `.json` file to be parsed. + * @param {string} [options.token] Personal access token to fetch workspace environments. + * @param {string} [options.server] server URL for SH instance. + * @returns {Promise} A promise that resolves to the parsed environment object with global and selected environments. */ -export async function parseEnvsData(path: string) { - const contents = await readJsonFile(path); +export async function parseEnvsData(options: TestCmdEnvironmentOptions) { + const { env: pathOrId, token: accessToken, server: serverUrl } = options; + + const contents = await getResourceContents({ + pathOrId, + accessToken, + serverUrl, + resourceType: "environment", + }); + const envPairs: Array> = []; // The legacy key-value pair format that is still supported @@ -33,7 +46,7 @@ export async function parseEnvsData(path: string) { // CLI doesnt support bulk environments export // Hence we check for this case and throw an error if it matches the format if (HoppBulkEnvExportObjectResult.success) { - throw error({ code: "BULK_ENV_FILE", path, data: error }); + throw error({ code: "BULK_ENV_FILE", path: pathOrId, data: error }); } // Checks if the environment file is of the correct format @@ -42,7 +55,7 @@ export async function parseEnvsData(path: string) { !HoppEnvKeyPairResult.success && HoppEnvExportObjectResult.type === "err" ) { - throw error({ code: "MALFORMED_ENV_FILE", path, data: error }); + throw error({ code: "MALFORMED_ENV_FILE", path: pathOrId, data: error }); } if (HoppEnvKeyPairResult.success) { @@ -50,7 +63,27 @@ export async function parseEnvsData(path: string) { envPairs.push({ key, value, secret: false }); } } else if (HoppEnvExportObjectResult.type === "ok") { - envPairs.push(...HoppEnvExportObjectResult.value.variables); + // Original environment variables from the supplied export file + const originalEnvVariables = (contents as NonSecretEnvironment).variables; + + // Above environment variables conforming to the latest schema + // `value` fields if specified will be omitted for secret environment variables + const migratedEnvVariables = HoppEnvExportObjectResult.value.variables; + + // The values supplied for secret environment variables have to be considered in the CLI + // For each secret environment variable, include the value in case supplied + const resolvedEnvVariables = migratedEnvVariables.map((variable, idx) => { + if (variable.secret && originalEnvVariables[idx].value) { + return { + ...variable, + value: originalEnvVariables[idx].value, + }; + } + + return variable; + }); + + envPairs.push(...resolvedEnvVariables); } return { global: [], selected: envPairs }; diff --git a/packages/hoppscotch-cli/src/types/collections.ts b/packages/hoppscotch-cli/src/types/collections.ts index f5483e2d4a..08c6ccb759 100644 --- a/packages/hoppscotch-cli/src/types/collections.ts +++ b/packages/hoppscotch-cli/src/types/collections.ts @@ -1,10 +1,15 @@ import { HoppCollection } from "@hoppscotch/data"; -import { HoppEnvs } from "./request"; +import { HoppEnvPair, HoppEnvs } from "./request"; export type CollectionRunnerParam = { collections: HoppCollection[]; envs: HoppEnvs; delay?: number; + iterationData?: IterationDataItem[][]; + iterationCount?: number; }; export type HoppCollectionFileExt = "json"; + +// Indicates the shape each iteration data entry gets transformed into +export type IterationDataItem = Extract; diff --git a/packages/hoppscotch-cli/src/types/commands.ts b/packages/hoppscotch-cli/src/types/commands.ts index 0c8eab8554..e353cb7d3a 100644 --- a/packages/hoppscotch-cli/src/types/commands.ts +++ b/packages/hoppscotch-cli/src/types/commands.ts @@ -1,6 +1,19 @@ export type TestCmdOptions = { - env: string | undefined; - delay: string | undefined; + env?: string; + delay?: string; + token?: string; + server?: string; + reporterJunit?: string; + iterationCount?: number; + iterationData?: string; +}; + +// Consumed in the collection `file_path_or_id` argument action handler +export type TestCmdCollectionOptions = Omit; + +// Consumed in the `--env, -e` flag action handler +export type TestCmdEnvironmentOptions = Omit & { + env: string; }; export type HOPP_ENV_FILE_EXT = "json"; diff --git a/packages/hoppscotch-cli/src/types/errors.ts b/packages/hoppscotch-cli/src/types/errors.ts index 0e75b74479..6122ff7974 100644 --- a/packages/hoppscotch-cli/src/types/errors.ts +++ b/packages/hoppscotch-cli/src/types/errors.ts @@ -26,6 +26,13 @@ type HoppErrors = { MALFORMED_ENV_FILE: HoppErrorPath & HoppErrorData; BULK_ENV_FILE: HoppErrorPath & HoppErrorData; INVALID_FILE_TYPE: HoppErrorData; + INVALID_DATA_FILE_TYPE: HoppErrorData; + TOKEN_EXPIRED: HoppErrorData; + TOKEN_INVALID: HoppErrorData; + INVALID_ID: HoppErrorData; + INVALID_SERVER_URL: HoppErrorData; + SERVER_CONNECTION_REFUSED: HoppErrorData; + REPORT_EXPORT_FAILED: HoppErrorPath & HoppErrorData; }; export type HoppErrorCode = keyof HoppErrors; diff --git a/packages/hoppscotch-cli/src/types/request.ts b/packages/hoppscotch-cli/src/types/request.ts index 21dffbd1c2..0f6f780f50 100644 --- a/packages/hoppscotch-cli/src/types/request.ts +++ b/packages/hoppscotch-cli/src/types/request.ts @@ -7,6 +7,7 @@ import { HoppCLIError } from "./errors"; export type FormDataEntry = { key: string; value: string | Blob; + contentType?: string; }; export type HoppEnvPair = Environment["variables"][number]; @@ -18,7 +19,7 @@ export type HoppEnvs = { selected: HoppEnvPair[]; }; -export type CollectionStack = { +export type CollectionQueue = { path: string; collection: HoppCollection; }; diff --git a/packages/hoppscotch-cli/src/utils/auth/digest.ts b/packages/hoppscotch-cli/src/utils/auth/digest.ts new file mode 100644 index 0000000000..7c8d65ddd5 --- /dev/null +++ b/packages/hoppscotch-cli/src/utils/auth/digest.ts @@ -0,0 +1,158 @@ +import axios from "axios"; +import { md5 } from "js-md5"; + +import { exceptionColors } from "../getters"; + +export interface DigestAuthParams { + username: string; + password: string; + realm: string; + nonce: string; + endpoint: string; + method: string; + algorithm: string; + qop: string; + nc?: string; + opaque?: string; + cnonce?: string; // client nonce (optional but typically required in qop='auth') + reqBody?: string; +} + +export interface DigestAuthInfo { + realm: string; + nonce: string; + qop: string; + opaque?: string; + algorithm: string; +} + +// Utility function to parse Digest auth header values +const parseDigestAuthHeader = ( + header: string +): { [key: string]: string } | null => { + const matches = header.match(/([a-z0-9]+)="([^"]+)"/gi); + if (!matches) return null; + + const authParams: { [key: string]: string } = {}; + matches.forEach((match) => { + const parts = match.split("="); + authParams[parts[0]] = parts[1].replace(/"/g, ""); + }); + + return authParams; +}; + +// Function to generate Digest Auth Header +export const generateDigestAuthHeader = async (params: DigestAuthParams) => { + const { + username, + password, + realm, + nonce, + endpoint, + method, + algorithm = "MD5", + qop, + nc = "00000001", + opaque, + cnonce, + reqBody = "", + } = params; + + const url = new URL(endpoint); + const uri = url.pathname + url.search; + + // Generate client nonce if not provided + const generatedCnonce = cnonce || md5(`${Math.random()}`); + + // Step 1: Hash the username, realm, password and any additional fields based on the algorithm + const ha1 = + algorithm === "MD5-sess" + ? md5( + `${md5(`${username}:${realm}:${password}`)}:${nonce}:${generatedCnonce}` + ) + : md5(`${username}:${realm}:${password}`); + + // Step 2: Hash the method and URI + const ha2 = + qop === "auth-int" + ? md5(`${method}:${uri}:${md5(reqBody)}`) // Entity body hash for `auth-int` + : md5(`${method}:${uri}`); + + // Step 3: Compute the response hash + const response = md5( + `${ha1}:${nonce}:${nc}:${generatedCnonce}:${qop}:${ha2}` + ); + + // Build the Digest header + let authHeader = `Digest username="${username}", realm="${realm}", nonce="${nonce}", uri="${uri}", algorithm="${algorithm}", response="${response}", qop=${qop}, nc=${nc}, cnonce="${generatedCnonce}"`; + + if (opaque) { + authHeader += `, opaque="${opaque}"`; + } + + return authHeader; +}; + +export const fetchInitialDigestAuthInfo = async ( + url: string, + method: string, + disableRetry: boolean +): Promise => { + try { + const initialResponse = await axios.request({ + url, + method, + validateStatus: () => true, // Allow handling of all status codes + }); + + if (disableRetry) { + throw new Error( + `Received status: ${initialResponse.status}. Retry is disabled as specified, so no further attempts will be made.` + ); + } + + // Check if the response status is 401 (which is expected in Digest Auth flow) + if (initialResponse.status === 401) { + const authHeaderEntry = Object.keys(initialResponse.headers).find( + (header) => header.toLowerCase() === "www-authenticate" + ); + + const authHeader = authHeaderEntry + ? (initialResponse.headers[authHeaderEntry] ?? null) + : null; + + if (authHeader) { + const authParams = parseDigestAuthHeader(authHeader); + if ( + authParams && + authParams.realm && + authParams.nonce && + authParams.qop + ) { + return { + realm: authParams.realm, + nonce: authParams.nonce, + qop: authParams.qop, + opaque: authParams.opaque, + algorithm: authParams.algorithm, + }; + } + } + throw new Error( + "Failed to parse authentication parameters from WWW-Authenticate header" + ); + } + + throw new Error(`Unexpected response: ${initialResponse.status}`); + } catch (error) { + const errMsg = error instanceof Error ? error.message : error; + + console.error( + exceptionColors.FAIL( + `\n Error fetching initial digest auth info: ${errMsg} \n` + ) + ); + throw error; // Re-throw the error to handle it further up the chain if needed + } +}; diff --git a/packages/hoppscotch-cli/src/utils/collections.ts b/packages/hoppscotch-cli/src/utils/collections.ts index c4708c8179..5c81d5d7dc 100644 --- a/packages/hoppscotch-cli/src/utils/collections.ts +++ b/packages/hoppscotch-cli/src/utils/collections.ts @@ -7,7 +7,7 @@ import { round } from "lodash-es"; import { CollectionRunnerParam } from "../types/collections"; import { - CollectionStack, + CollectionQueue, HoppEnvs, ProcessRequestParams, RequestReport, @@ -27,6 +27,7 @@ import { } from "./display"; import { exceptionColors } from "./getters"; import { getPreRequestMetrics } from "./pre-request"; +import { buildJUnitReport, generateJUnitReportExport } from "./reporters/junit"; import { getRequestMetrics, preProcessRequest, @@ -34,7 +35,7 @@ import { } from "./request"; import { getTestMetrics } from "./test"; -const { WARN, FAIL } = exceptionColors; +const { WARN, FAIL, INFO } = exceptionColors; /** * Processes each requests within collections to prints details of subsequent requests, @@ -42,85 +43,134 @@ const { WARN, FAIL } = exceptionColors; * @param param Data of hopp-collection with hopp-requests, envs to be processed. * @returns List of report for each processed request. */ + export const collectionsRunner = async ( param: CollectionRunnerParam ): Promise => { - const envs: HoppEnvs = param.envs; - const delay = param.delay ?? 0; + const { collections, envs, delay, iterationCount, iterationData } = param; + + const resolvedDelay = delay ?? 0; + const requestsReport: RequestReport[] = []; - const collectionStack: CollectionStack[] = getCollectionStack( - param.collections - ); + const collectionQueue = getCollectionQueue(collections); - while (collectionStack.length) { - // Pop out top-most collection from stack to be processed. - const { collection, path } = collectionStack.pop(); - - // Processing each request in collection - for (const request of collection.requests) { - const _request = preProcessRequest(request as HoppRESTRequest, collection); - const requestPath = `${path}/${_request.name}`; - const processRequestParams: ProcessRequestParams = { - path: requestPath, - request: _request, - envs, - delay, - }; - - // Request processing initiated message. - log(WARN(`\nRunning: ${chalk.bold(requestPath)}`)); - - // Processing current request. - const result = await processRequest(processRequestParams)(); - - // Updating global & selected envs with new envs from processed-request output. - const { global, selected } = result.envs; - envs.global = global; - envs.selected = selected; - - // Storing current request's report. - const requestReport = result.report; - requestsReport.push(requestReport); - } - - // Pushing remaining folders realted collection to stack. - for (const folder of collection.folders) { - const updatedFolder: HoppCollection = { ...folder } - - if (updatedFolder.auth?.authType === "inherit") { - updatedFolder.auth = collection.auth; - } + // If iteration count is not supplied, it should be based on the size of iteration data if in scope + const resolvedIterationCount = iterationCount ?? iterationData?.length ?? 1; - if (collection.headers?.length) { - // Filter out header entries present in the parent collection under the same name - // This ensures the folder headers take precedence over the collection headers - const filteredHeaders = collection.headers.filter((collectionHeaderEntries) => { - return !updatedFolder.headers.some((folderHeaderEntries) => folderHeaderEntries.key === collectionHeaderEntries.key) - }) - updatedFolder.headers.push(...filteredHeaders); - } + const originalSelectedEnvs = [...envs.selected]; - collectionStack.push({ - path: `${path}/${updatedFolder.name}`, - collection: updatedFolder, - }); - } + for (let count = 0; count < resolvedIterationCount; count++) { + if (resolvedIterationCount > 1) { + log(INFO(`\nIteration: ${count + 1}/${resolvedIterationCount}`)); } + // Reset `envs` to the original value at the start of each iteration + envs.selected = [...originalSelectedEnvs]; + + if (iterationData) { + // Ensure last item is picked if the iteration count exceeds size of the iteration data + const iterationDataItem = + iterationData[Math.min(count, iterationData.length - 1)]; + + // Ensure iteration data takes priority over supplied environment variables + envs.selected = envs.selected + .filter( + (envPair) => + !iterationDataItem.some((dataPair) => dataPair.key === envPair.key) + ) + .concat(iterationDataItem); + } + + for (const { collection, path } of collectionQueue) { + await processCollection( + collection, + path, + envs, + resolvedDelay, + requestsReport + ); + } + } + return requestsReport; }; +const processCollection = async ( + collection: HoppCollection, + path: string, + envs: HoppEnvs, + delay: number, + requestsReport: RequestReport[] +) => { + // Process each request in the collection + for (const request of collection.requests) { + const _request = preProcessRequest(request as HoppRESTRequest, collection); + const requestPath = `${path}/${_request.name}`; + const processRequestParams: ProcessRequestParams = { + path: requestPath, + request: _request, + envs, + delay, + }; + + // Request processing initiated message. + log(WARN(`\nRunning: ${chalk.bold(requestPath)}`)); + + // Processing current request. + const result = await processRequest(processRequestParams)(); + + // Updating global & selected envs with new envs from processed-request output. + const { global, selected } = result.envs; + envs.global = global; + envs.selected = selected; + + // Storing current request's report. + const requestReport = result.report; + requestsReport.push(requestReport); + } + + // Process each folder in the collection + for (const folder of collection.folders) { + const updatedFolder: HoppCollection = { ...folder }; + + if (updatedFolder.auth?.authType === "inherit") { + updatedFolder.auth = collection.auth; + } + + if (collection.headers?.length) { + // Filter out header entries present in the parent collection under the same name + // This ensures the folder headers take precedence over the collection headers + const filteredHeaders = collection.headers.filter( + (collectionHeaderEntries) => { + return !updatedFolder.headers.some( + (folderHeaderEntries) => + folderHeaderEntries.key === collectionHeaderEntries.key + ); + } + ); + updatedFolder.headers.push(...filteredHeaders); + } + + await processCollection( + updatedFolder, + `${path}/${updatedFolder.name}`, + envs, + delay, + requestsReport + ); + } +}; /** * Transforms collections to generate collection-stack which describes each collection's * path within collection & the collection itself. * @param collections Hopp-collection objects to be mapped to collection-stack type. * @returns Mapped collections to collection-stack. */ -const getCollectionStack = (collections: HoppCollection[]): CollectionStack[] => +const getCollectionQueue = (collections: HoppCollection[]): CollectionQueue[] => pipe( collections, A.map( - (collection) => { collection, path: collection.name } + (collection) => { collection, path: collection.name } ) ); @@ -134,7 +184,8 @@ const getCollectionStack = (collections: HoppCollection[]): CollectionStack[] => * False, if errors occurred or test-cases failed. */ export const collectionsRunnerResult = ( - requestsReport: RequestReport[] + requestsReport: RequestReport[], + reporterJUnitExportPath?: string ): boolean => { const overallTestMetrics = { tests: { failed: 0, passed: 0 }, @@ -152,6 +203,9 @@ export const collectionsRunnerResult = ( }; let finalResult = true; + let totalErroredTestCases = 0; + let totalFailedTestCases = 0; + // Printing requests-report details of failed-tests and errors for (const requestReport of requestsReport) { const { path, tests, errors, result, duration } = requestReport; @@ -165,6 +219,19 @@ export const collectionsRunnerResult = ( printErrorsReport(path, errors); + if (reporterJUnitExportPath) { + const { failedRequestTestCases, erroredRequestTestCases } = + buildJUnitReport({ + path, + tests, + errors, + duration: duration.test, + }); + + totalFailedTestCases += failedRequestTestCases; + totalErroredTestCases += erroredRequestTestCases; + } + /** * Extracting current request report's test-metrics and updating * overall test-metrics. @@ -216,6 +283,19 @@ export const collectionsRunnerResult = ( printRequestsMetrics(overallRequestMetrics); printPreRequestMetrics(overallPreRequestMetrics); + if (reporterJUnitExportPath) { + const totalTestCases = + overallTestMetrics.tests.failed + overallTestMetrics.tests.passed; + + generateJUnitReportExport({ + totalTestCases, + totalFailedTestCases, + totalErroredTestCases, + testDuration: overallTestMetrics.duration, + reporterJUnitExportPath, + }); + } + return finalResult; }; diff --git a/packages/hoppscotch-cli/src/utils/getters.ts b/packages/hoppscotch-cli/src/utils/getters.ts index 5bbb2b8ee4..07fe43fc20 100644 --- a/packages/hoppscotch-cli/src/utils/getters.ts +++ b/packages/hoppscotch-cli/src/utils/getters.ts @@ -1,18 +1,36 @@ import { + EnvironmentVariable, HoppRESTHeader, - Environment, - parseTemplateStringE, HoppRESTParam, + HoppRESTRequestVariables, + parseTemplateStringE, } from "@hoppscotch/data"; +import axios, { AxiosError } from "axios"; import chalk from "chalk"; -import { pipe } from "fp-ts/function"; import * as A from "fp-ts/Array"; import * as E from "fp-ts/Either"; -import * as S from "fp-ts/string"; import * as O from "fp-ts/Option"; -import { error } from "../types/errors"; +import { pipe } from "fp-ts/function"; +import * as S from "fp-ts/string"; +import fs from "fs/promises"; import { round } from "lodash-es"; + +import { error } from "../types/errors"; import { DEFAULT_DURATION_PRECISION } from "./constants"; +import { readJsonFile } from "./mutators"; +import { + WorkspaceCollection, + WorkspaceEnvironment, + transformWorkspaceCollections, + transformWorkspaceEnvironment, +} from "./workspace-access"; + +type GetResourceContentsParams = { + pathOrId: string; + accessToken?: string; + serverUrl?: string; + resourceType: "collection" | "environment"; +}; /** * Generates template string (status + statusText) with specific color unicodes @@ -40,12 +58,12 @@ export const getColorStatusCode = ( * Replaces all template-string with their effective ENV values to generate effective * request headers/parameters meta-data. * @param metaData Headers/parameters on which ENVs will be applied. - * @param environment Provides ENV variables for parsing template-string. + * @param resolvedVariables Provides ENV variables for parsing template-string. * @returns Active, non-empty-key, parsed headers/parameters pairs. */ export const getEffectiveFinalMetaData = ( metaData: HoppRESTHeader[] | HoppRESTParam[], - environment: Environment + resolvedVariables: EnvironmentVariable[] ) => pipe( metaData, @@ -54,11 +72,14 @@ export const getEffectiveFinalMetaData = ( * Selecting only non-empty and active pairs. */ A.filter(({ key, active }) => !S.isEmpty(key) && active), - A.map(({ key, value }) => ({ - active: true, - key: parseTemplateStringE(key, environment.variables), - value: parseTemplateStringE(value, environment.variables), - })), + A.map(({ key, value, description }) => { + return { + active: true, + key: parseTemplateStringE(key, resolvedVariables), + value: parseTemplateStringE(value, resolvedVariables), + description, + }; + }), E.fromPredicate( /** * Check if every key-value is right either. Else return HoppCLIError with @@ -71,9 +92,14 @@ export const getEffectiveFinalMetaData = ( /** * Filtering and mapping only right-eithers for each key-value as [string, string]. */ - A.filterMap(({ key, value }) => + A.filterMap(({ key, value, description }) => E.isRight(key) && E.isRight(value) - ? O.some({ active: true, key: key.right, value: value.right }) + ? O.some({ + active: true, + key: key.right, + value: value.right, + description, + }) : O.none ) ) @@ -134,3 +160,131 @@ export const roundDuration = ( duration: number, precision: number = DEFAULT_DURATION_PRECISION ) => round(duration, precision); + +/** + * Retrieves the contents of a resource (collection or environment) from a local file (export) or a remote server (workspaces). + * + * @param {GetResourceContentsParams} params - The parameters for retrieving resource contents. + * @param {string} params.pathOrId - The path to the local file or the ID for remote retrieval. + * @param {string} [params.accessToken] - The access token for authorizing remote retrieval. + * @param {string} [params.serverUrl] - The SH instance server URL for remote retrieval. Defaults to the cloud instance. + * @param {"collection" | "environment"} params.resourceType - The type of the resource to retrieve. + * @returns {Promise} A promise that resolves to the contents of the resource. + * @throws Will throw an error if the content type of the fetched resource is not `application/json`, + * if there is an issue with the access token, if the server connection is refused, + * or if the server URL is invalid. + */ +export const getResourceContents = async ( + params: GetResourceContentsParams +): Promise => { + const { pathOrId, accessToken, serverUrl, resourceType } = params; + + let contents: unknown | null = null; + let fileExistsInPath = false; + + try { + await fs.access(pathOrId); + fileExistsInPath = true; + } catch (e) { + fileExistsInPath = false; + } + + if (accessToken && !fileExistsInPath) { + const resolvedServerUrl = serverUrl || "https://api.hoppscotch.io"; + + try { + const separator = resolvedServerUrl.endsWith("/") ? "" : "/"; + const resourcePath = + resourceType === "collection" ? "collection" : "environment"; + + const url = `${resolvedServerUrl}${separator}v1/access-tokens/${resourcePath}/${pathOrId}`; + + const { data, headers } = await axios.get(url, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + if (!headers["content-type"].includes("application/json")) { + throw new AxiosError("INVALID_CONTENT_TYPE"); + } + + contents = + resourceType === "collection" + ? transformWorkspaceCollections([data] as WorkspaceCollection[])[0] + : transformWorkspaceEnvironment(data as WorkspaceEnvironment); + } catch (err) { + if (err instanceof AxiosError) { + const axiosErr: AxiosError<{ + reason?: "TOKEN_EXPIRED" | "TOKEN_INVALID" | "INVALID_ID"; + message: string; + statusCode: number; + }> = err; + + const errReason = axiosErr.response?.data?.reason; + + if (errReason) { + throw error({ + code: errReason, + data: ["TOKEN_EXPIRED", "TOKEN_INVALID"].includes(errReason) + ? accessToken + : pathOrId, + }); + } + + if (axiosErr.code === "ECONNREFUSED") { + throw error({ + code: "SERVER_CONNECTION_REFUSED", + data: resolvedServerUrl, + }); + } + + if ( + axiosErr.message === "INVALID_CONTENT_TYPE" || + axiosErr.code === "ERR_INVALID_URL" || + axiosErr.code === "ENOTFOUND" || + axiosErr.code === "ERR_BAD_REQUEST" || + axiosErr.response?.status === 404 + ) { + throw error({ code: "INVALID_SERVER_URL", data: resolvedServerUrl }); + } + } else { + throw error({ code: "UNKNOWN_ERROR", data: err }); + } + } + } + + // Fallback to reading from file if contents are not available + if (contents === null) { + contents = await readJsonFile(pathOrId, fileExistsInPath); + } + + return contents; +}; + +/** + * Processes incoming request variables and environment variables and returns a list + * where active request variables are picked and prioritised over the supplied environment variables. + * Falls back to environment variables for an empty request variable. + * + * @param {HoppRESTRequestVariables} requestVariables - Incoming request variables. + * @param {EnvironmentVariable[]} environmentVariables - Incoming environment variables. + * @returns {EnvironmentVariable[]} The resolved list of variables that conforms to the shape of environment variables. + */ +export const getResolvedVariables = ( + requestVariables: HoppRESTRequestVariables, + environmentVariables: EnvironmentVariable[] +): EnvironmentVariable[] => { + const activeRequestVariables = requestVariables + .filter(({ active, value }) => active && value) + .map(({ key, value }) => ({ key, value, secret: false })); + + const requestVariableKeys = activeRequestVariables.map(({ key }) => key); + + // Request variables have higher priority, hence filtering out environment variables with the same keys + const filteredEnvironmentVariables = environmentVariables.filter( + ({ key }) => !requestVariableKeys.includes(key) + ); + + return [...activeRequestVariables, ...filteredEnvironmentVariables]; +}; diff --git a/packages/hoppscotch-cli/src/utils/mutators.ts b/packages/hoppscotch-cli/src/utils/mutators.ts index 91b1383f4e..153e66b24a 100644 --- a/packages/hoppscotch-cli/src/utils/mutators.ts +++ b/packages/hoppscotch-cli/src/utils/mutators.ts @@ -1,11 +1,48 @@ -import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"; +import { Environment, HoppCollection, HoppRESTRequest } from "@hoppscotch/data"; import fs from "fs/promises"; import { entityReference } from "verzod"; import { z } from "zod"; +import { TestCmdCollectionOptions } from "../types/commands"; import { error } from "../types/errors"; import { FormDataEntry } from "../types/request"; import { isHoppErrnoException } from "./checks"; +import { getResourceContents } from "./getters"; + +const getValidRequests = ( + collections: HoppCollection[], + collectionFilePath: string +) => { + return collections.map((collection) => { + // Validate requests using zod schema + const requestSchemaParsedResult = z + .array(entityReference(HoppRESTRequest)) + .safeParse(collection.requests); + + // Handle validation errors + if (!requestSchemaParsedResult.success) { + throw error({ + code: "MALFORMED_COLLECTION", + path: collectionFilePath, + data: "Please check the collection data.", + }); + } + + // Recursively validate requests in nested folders + if (collection.folders.length > 0) { + collection.folders = getValidRequests( + collection.folders, + collectionFilePath + ); + } + + // Return validated collection + return { + ...collection, + requests: requestSchemaParsedResult.data, + }; + }); +}; /** * Parses array of FormDataEntry to FormData. @@ -15,7 +52,21 @@ import { isHoppErrnoException } from "./checks"; export const toFormData = (values: FormDataEntry[]) => { const formData = new FormData(); - values.forEach(({ key, value }) => formData.append(key, value)); + values.forEach(({ key, value, contentType }) => { + if (contentType) { + formData.append( + key, + new Blob([value], { + type: contentType, + }), + key + ); + + return; + } + + formData.append(key, value); + }); return formData; }; @@ -37,15 +88,26 @@ export const parseErrorMessage = (e: unknown) => { return msg.replace(/\n+$|\s{2,}/g, "").trim(); }; -export async function readJsonFile(path: string): Promise { +/** + * Reads a JSON file from the specified path and returns the parsed content. + * + * @param {string} path - The path to the JSON file. + * @param {boolean} fileExistsInPath - Indicates whether the file exists in the specified path. + * @returns {Promise} A Promise that resolves to the parsed JSON contents. + * @throws {Error} If the file path does not end with `.json`. + * @throws {Error} If the file does not exist in the specified path. + * @throws {Error} If an unknown error occurs while reading or parsing the file. + */ +export async function readJsonFile( + path: string, + fileExistsInPath: boolean +): Promise { if (!path.endsWith(".json")) { throw error({ code: "INVALID_FILE_TYPE", data: path }); } - try { - await fs.access(path); - } catch (e) { - throw error({ code: "FILE_NOT_FOUND", path: path }); + if (!fileExistsInPath) { + throw error({ code: "FILE_NOT_FOUND", path }); } try { @@ -56,15 +118,27 @@ export async function readJsonFile(path: string): Promise { } /** - * Parses collection json file for given path:context.path, and validates - * the parsed collectiona array. - * @param path Collection json file path. - * @returns For successful parsing we get array of HoppCollection, + * Parses collection data from a given path or ID and returns the data conforming to the latest version of the `HoppCollection` schema. + * + * @param pathOrId Collection JSON file path/ID from a workspace. + * @param {TestCmdCollectionOptions} options Supplied values for CLI flags. + * @param {string} [options.token] Personal access token to fetch workspace environments. + * @param {string} [options.server] server URL for SH instance. + * @returns {Promise} A promise that resolves to an array of HoppCollection objects. + * @throws Throws an error if the collection data is malformed. */ export async function parseCollectionData( - path: string + pathOrId: string, + options: TestCmdCollectionOptions ): Promise { - let contents = await readJsonFile(path); + const { token: accessToken, server: serverUrl } = options; + + const contents = await getResourceContents({ + pathOrId, + accessToken, + serverUrl, + resourceType: "collection", + }); const maybeArrayOfCollections: unknown[] = Array.isArray(contents) ? contents @@ -77,27 +151,10 @@ export async function parseCollectionData( if (!collectionSchemaParsedResult.success) { throw error({ code: "MALFORMED_COLLECTION", - path, + path: pathOrId, data: "Please check the collection data.", }); } - return collectionSchemaParsedResult.data.map((collection) => { - const requestSchemaParsedResult = z - .array(entityReference(HoppRESTRequest)) - .safeParse(collection.requests); - - if (!requestSchemaParsedResult.success) { - throw error({ - code: "MALFORMED_COLLECTION", - path, - data: "Please check the collection data.", - }); - } - - return { - ...collection, - requests: requestSchemaParsedResult.data, - }; - }); + return getValidRequests(collectionSchemaParsedResult.data, pathOrId); } diff --git a/packages/hoppscotch-cli/src/utils/pre-request.ts b/packages/hoppscotch-cli/src/utils/pre-request.ts index c4227c790a..de45631324 100644 --- a/packages/hoppscotch-cli/src/utils/pre-request.ts +++ b/packages/hoppscotch-cli/src/utils/pre-request.ts @@ -1,5 +1,6 @@ import { Environment, + EnvironmentVariable, HoppRESTRequest, parseBodyEnvVariablesE, parseRawKeyValueEntriesE, @@ -15,6 +16,7 @@ import * as TE from "fp-ts/TaskEither"; import { flow, pipe } from "fp-ts/function"; import * as S from "fp-ts/string"; import qs from "qs"; +import { AwsV4Signer } from "aws4fetch"; import { EffectiveHoppRESTRequest } from "../interfaces/request"; import { HoppCLIError, error } from "../types/errors"; @@ -22,8 +24,13 @@ import { HoppEnvs } from "../types/request"; import { PreRequestMetrics } from "../types/response"; import { isHoppCLIError } from "./checks"; import { arrayFlatMap, arraySort, tupleToRecord } from "./functions/array"; -import { getEffectiveFinalMetaData } from "./getters"; +import { getEffectiveFinalMetaData, getResolvedVariables } from "./getters"; import { toFormData } from "./mutators"; +import { + DigestAuthParams, + fetchInitialDigestAuthInfo, + generateDigestAuthHeader, +} from "./auth/digest"; /** * Runs pre-request-script runner over given request which extracts set ENVs and @@ -47,9 +54,18 @@ export const preRequestScriptRunner = ( ), TE.map( ({ selected, global }) => - { name: "Env", variables: [...selected, ...global] } + { + name: "Env", + variables: [...(selected ?? []), ...(global ?? [])], + } + ), + TE.chainW((env) => + TE.tryCatch( + () => getEffectiveRESTRequest(request, env), + (reason) => error({ code: "PRE_REQUEST_SCRIPT_ERROR", data: reason }) + ) ), - TE.chainEitherKW((env) => getEffectiveRESTRequest(request, env)), + TE.chainEitherKW((effectiveRequest) => effectiveRequest), TE.mapLeft((reason) => isHoppCLIError(reason) ? reason @@ -68,19 +84,26 @@ export const preRequestScriptRunner = ( * * @returns An object with extra fields defining a complete request */ -export function getEffectiveRESTRequest( +export async function getEffectiveRESTRequest( request: HoppRESTRequest, environment: Environment -): E.Either< - HoppCLIError, - { effectiveRequest: EffectiveHoppRESTRequest } & { updatedEnvs: HoppEnvs } +): Promise< + E.Either< + HoppCLIError, + { effectiveRequest: EffectiveHoppRESTRequest } & { updatedEnvs: HoppEnvs } + > > { const envVariables = environment.variables; + const resolvedVariables = getResolvedVariables( + request.requestVariables, + envVariables + ); + // Parsing final headers with applied ENVs. const _effectiveFinalHeaders = getEffectiveFinalMetaData( request.headers, - environment + resolvedVariables ); if (E.isLeft(_effectiveFinalHeaders)) { return _effectiveFinalHeaders; @@ -90,30 +113,47 @@ export function getEffectiveRESTRequest( // Parsing final parameters with applied ENVs. const _effectiveFinalParams = getEffectiveFinalMetaData( request.params, - environment + resolvedVariables ); if (E.isLeft(_effectiveFinalParams)) { return _effectiveFinalParams; } const effectiveFinalParams = _effectiveFinalParams.right; + // Parsing final-body with applied ENVs. + const _effectiveFinalBody = getFinalBodyFromRequest( + request, + resolvedVariables + ); + if (E.isLeft(_effectiveFinalBody)) { + return _effectiveFinalBody; + } + // Authentication if (request.auth.authActive) { // TODO: Support a better b64 implementation than btoa ? if (request.auth.authType === "basic") { - const username = parseTemplateString(request.auth.username, envVariables); - const password = parseTemplateString(request.auth.password, envVariables); + const username = parseTemplateString( + request.auth.username, + resolvedVariables + ); + const password = parseTemplateString( + request.auth.password, + resolvedVariables + ); effectiveFinalHeaders.push({ active: true, key: "Authorization", value: `Basic ${btoa(`${username}:${password}`)}`, + description: "", }); } else if (request.auth.authType === "bearer") { effectiveFinalHeaders.push({ active: true, key: "Authorization", - value: `Bearer ${parseTemplateString(request.auth.token, envVariables)}`, + value: `Bearer ${parseTemplateString(request.auth.token, resolvedVariables)}`, + description: "", }); } else if (request.auth.authType === "oauth-2") { const { addTo } = request.auth; @@ -122,7 +162,8 @@ export function getEffectiveRESTRequest( effectiveFinalHeaders.push({ active: true, key: "Authorization", - value: `Bearer ${parseTemplateString(request.auth.grantTypeInfo.token, envVariables)}`, + value: `Bearer ${parseTemplateString(request.auth.grantTypeInfo.token, resolvedVariables)}`, + description: "", }); } else if (addTo === "QUERY_PARAMS") { effectiveFinalParams.push({ @@ -130,46 +171,145 @@ export function getEffectiveRESTRequest( key: "access_token", value: parseTemplateString( request.auth.grantTypeInfo.token, - envVariables + resolvedVariables ), + description: "", }); } } else if (request.auth.authType === "api-key") { const { key, value, addTo } = request.auth; - if (addTo === "Headers") { + if (addTo === "HEADERS") { effectiveFinalHeaders.push({ active: true, - key: parseTemplateString(key, envVariables), - value: parseTemplateString(value, envVariables), + key: parseTemplateString(key, resolvedVariables), + value: parseTemplateString(value, resolvedVariables), + description: "", }); - } else if (addTo === "Query params") { + } else if (addTo === "QUERY_PARAMS") { effectiveFinalParams.push({ active: true, - key: parseTemplateString(key, envVariables), - value: parseTemplateString(value, envVariables), + key: parseTemplateString(key, resolvedVariables), + value: parseTemplateString(value, resolvedVariables), + description: "", + }); + } + } else if (request.auth.authType === "aws-signature") { + const { addTo } = request.auth; + + const currentDate = new Date(); + const amzDate = currentDate.toISOString().replace(/[:-]|\.\d{3}/g, ""); + const { method, endpoint } = request; + + const signer = new AwsV4Signer({ + method, + datetime: amzDate, + signQuery: addTo === "QUERY_PARAMS", + accessKeyId: parseTemplateString( + request.auth.accessKey, + resolvedVariables + ), + secretAccessKey: parseTemplateString( + request.auth.secretKey, + resolvedVariables + ), + region: + parseTemplateString(request.auth.region, resolvedVariables) ?? + "us-east-1", + service: parseTemplateString( + request.auth.serviceName, + resolvedVariables + ), + url: parseTemplateString(endpoint, resolvedVariables), + sessionToken: + request.auth.serviceToken && + parseTemplateString(request.auth.serviceToken, resolvedVariables), + }); + + const sign = await signer.sign(); + + if (addTo === "HEADERS") { + sign.headers.forEach((value, key) => { + effectiveFinalHeaders.push({ + active: true, + key, + value, + description: "", + }); + }); + } else if (addTo === "QUERY_PARAMS") { + sign.url.searchParams.forEach((value, key) => { + effectiveFinalParams.push({ + active: true, + key, + value, + description: "", + }); }); } + } else if (request.auth.authType === "digest") { + const { method, endpoint } = request as HoppRESTRequest; + + // Step 1: Fetch the initial auth info (nonce, realm, etc.) + const authInfo = await fetchInitialDigestAuthInfo( + parseTemplateString(endpoint, resolvedVariables), + method, + request.auth.disableRetry + ); + + // Step 2: Set up the parameters for the digest authentication header + const digestAuthParams: DigestAuthParams = { + username: parseTemplateString(request.auth.username, resolvedVariables), + password: parseTemplateString(request.auth.password, resolvedVariables), + realm: request.auth.realm + ? parseTemplateString(request.auth.realm, resolvedVariables) + : authInfo.realm, + nonce: request.auth.nonce + ? parseTemplateString(authInfo.nonce, resolvedVariables) + : authInfo.nonce, + endpoint: parseTemplateString(endpoint, resolvedVariables), + method, + algorithm: request.auth.algorithm ?? authInfo.algorithm, + qop: request.auth.qop + ? parseTemplateString(request.auth.qop, resolvedVariables) + : authInfo.qop, + opaque: request.auth.opaque + ? parseTemplateString(request.auth.opaque, resolvedVariables) + : authInfo.opaque, + reqBody: typeof request.body.body === "string" ? request.body.body : "", + }; + + // Step 3: Generate the Authorization header + const authHeaderValue = await generateDigestAuthHeader(digestAuthParams); + + effectiveFinalHeaders.push({ + active: true, + key: "Authorization", + value: authHeaderValue, + description: "", + }); } } - // Parsing final-body with applied ENVs. - const _effectiveFinalBody = getFinalBodyFromRequest(request, envVariables); - if (E.isLeft(_effectiveFinalBody)) { - return _effectiveFinalBody; - } const effectiveFinalBody = _effectiveFinalBody.right; - if (request.body.contentType) + if ( + request.body.contentType && + !effectiveFinalHeaders.some( + ({ key }) => key.toLowerCase() === "content-type" + ) + ) { effectiveFinalHeaders.push({ active: true, - key: "content-type", + key: "Content-Type", value: request.body.contentType, + description: "", }); + } - // Parsing final-endpoint with applied ENVs. + // Parsing final-endpoint with applied ENVs (environment + request variables). const _effectiveFinalURL = parseTemplateStringE( request.endpoint, - envVariables + resolvedVariables ); if (E.isLeft(_effectiveFinalURL)) { return E.left( @@ -186,7 +326,7 @@ export function getEffectiveRESTRequest( if (envVariables.some(({ secret }) => secret)) { const _effectiveFinalDisplayURL = parseTemplateStringE( request.endpoint, - envVariables, + resolvedVariables, true ); @@ -204,7 +344,7 @@ export function getEffectiveRESTRequest( effectiveFinalParams, effectiveFinalBody, }, - updatedEnvs: { global: [], selected: envVariables }, + updatedEnvs: { global: [], selected: resolvedVariables }, }); } @@ -212,15 +352,15 @@ export function getEffectiveRESTRequest( * Replaces template variables in request's body from the given set of ENVs, * to generate final request body without any template variables. * @param request Provides request's body, on which ENVs has to be applied. - * @param envVariables Provides set of key-value pairs (environment variables), + * @param resolvedVariables Provides set of key-value pairs (request + environment variables), * used to parse-out template variables. * @returns Final request body without any template variables as value. * Or, HoppCLIError in case of error while parsing. */ function getFinalBodyFromRequest( request: HoppRESTRequest, - envVariables: Environment["variables"] -): E.Either { + resolvedVariables: EnvironmentVariable[] +): E.Either { if (request.body.contentType === null) { return E.right(null); } @@ -243,8 +383,8 @@ function getFinalBodyFromRequest( * which will be resolved in further steps. */ A.map(({ key, value }) => [ - parseTemplateStringE(key, envVariables), - parseTemplateStringE(value, envVariables), + parseTemplateStringE(key, resolvedVariables), + parseTemplateStringE(value, resolvedVariables), ]), /** @@ -280,13 +420,15 @@ function getFinalBodyFromRequest( arrayFlatMap((x) => x.isFile ? x.value.map((v) => ({ - key: parseTemplateString(x.key, envVariables), + key: parseTemplateString(x.key, resolvedVariables), value: v as string | Blob, + contentType: x.contentType, })) : [ { - key: parseTemplateString(x.key, envVariables), - value: parseTemplateString(x.value, envVariables), + key: parseTemplateString(x.key, resolvedVariables), + value: parseTemplateString(x.value, resolvedVariables), + contentType: x.contentType, }, ] ), @@ -295,8 +437,22 @@ function getFinalBodyFromRequest( ); } + if (request.body.contentType === "application/octet-stream") { + const body = request.body.body; + + if (!body) { + return E.right(null); + } + + if (!(body instanceof File)) { + return E.right(null); + } + + return E.right(body); + } + return pipe( - parseBodyEnvVariablesE(request.body.body, envVariables), + parseBodyEnvVariablesE(request.body.body, resolvedVariables), E.mapLeft((e) => error({ code: "PARSING_ERROR", diff --git a/packages/hoppscotch-cli/src/utils/reporters/junit.ts b/packages/hoppscotch-cli/src/utils/reporters/junit.ts new file mode 100644 index 0000000000..a47fdf546e --- /dev/null +++ b/packages/hoppscotch-cli/src/utils/reporters/junit.ts @@ -0,0 +1,178 @@ +import { info, log } from "console"; +import fs from "fs"; +import path from "path"; + +import { create } from "xmlbuilder2"; +import { XMLBuilder } from "xmlbuilder2/lib/interfaces"; +import { TestReport } from "../../interfaces/response"; +import { error, HoppCLIError } from "../../types/errors"; +import { RequestReport } from "../../types/request"; +import { exceptionColors } from "../getters"; + +type BuildJUnitReportArgs = Omit & { + duration: RequestReport["duration"]["test"]; +}; + +type BuildJUnitReportResult = { + failedRequestTestCases: number; + erroredRequestTestCases: number; +}; + +type GenerateJUnitReportExportArgs = { + totalTestCases: number; + totalFailedTestCases: number; + totalErroredTestCases: number; + testDuration: number; + reporterJUnitExportPath: string; +}; + +const { INFO, SUCCESS } = exceptionColors; + +// Create the root XML element +const rootEl = create({ version: "1.0", encoding: "UTF-8" }).ele("testsuites"); + +/** + * Builds a JUnit report based on the provided request report. + * Creates a test suite at the request level populating the XML document structure. + * + * @param {BuildJUnitReportArgs} options - The options to build the JUnit report. + * @param {string} options.path - The path of the request. + * @param {TestReport[]} options.tests - The test suites for the request. + * @param {HoppCLIError[]} options.errors - The errors encountered during the request. + * @param {number} options.duration - Time taken to execute the test suite. + * @returns {BuildJUnitReportResult} An object containing the number of failed and errored test cases. + */ +export const buildJUnitReport = ({ + path, + tests: testSuites, + errors: requestTestSuiteErrors, + duration: testSuiteDuration, +}: BuildJUnitReportArgs): BuildJUnitReportResult => { + let requestTestSuiteError: XMLBuilder | null = null; + + // Create a test suite at the request level + const requestTestSuite = rootEl.ele("testsuite", { + name: path, + time: testSuiteDuration, + timestamp: new Date().toISOString(), + }); + + if (requestTestSuiteErrors.length > 0) { + requestTestSuiteError = requestTestSuite.ele("system-err"); + } + + let systemErrContent = ""; + + requestTestSuiteErrors.forEach((error) => { + let compiledError = error.code; + + if ("data" in error) { + compiledError += ` - ${error.data}`; + } + + // Append each error message with a newline for separation + systemErrContent += `\n${" ".repeat(6)}${compiledError}`; + }); + + // There'll be a single `CDATA` element compiling all the error messages + if (requestTestSuiteError) { + requestTestSuiteError.dat(systemErrContent); + } + + let requestTestCases = 0; + let erroredRequestTestCases = 0; + let failedRequestTestCases = 0; + + // Test suites correspond to `pw.test()` invocations + testSuites.forEach(({ descriptor, expectResults }) => { + requestTestCases += expectResults.length; + + expectResults.forEach(({ status, message }) => { + const testCase = requestTestSuite + .ele("testcase", { + name: `${descriptor} - ${message}`, + }) + .att("classname", path); + + if (status === "fail") { + failedRequestTestCases += 1; + + testCase + .ele("failure") + .att("type", "AssertionFailure") + .att("message", message); + } else if (status === "error") { + erroredRequestTestCases += 1; + + testCase.ele("error").att("message", message); + } + }); + }); + + requestTestSuite.att("tests", requestTestCases.toString()); + requestTestSuite.att("failures", failedRequestTestCases.toString()); + requestTestSuite.att("errors", erroredRequestTestCases.toString()); + + return { + failedRequestTestCases, + erroredRequestTestCases, + }; +}; + +/** + * Generates the built JUnit report export at the specified path. + * + * @param {GenerateJUnitReportExportArgs} options - The options to generate the JUnit report export. + * @param {number} options.totalTestCases - The total number of test cases. + * @param {number} options.totalFailedTestCases - The total number of failed test cases. + * @param {number} options.totalErroredTestCases - The total number of errored test cases. + * @param {number} options.testDuration - The total duration of test cases. + * @param {string} options.reporterJUnitExportPath - The path to export the JUnit report. + * @returns {void} + */ +export const generateJUnitReportExport = ({ + totalTestCases, + totalFailedTestCases, + totalErroredTestCases, + testDuration, + reporterJUnitExportPath, +}: GenerateJUnitReportExportArgs) => { + rootEl + .att("tests", totalTestCases.toString()) + .att("failures", totalFailedTestCases.toString()) + .att("errors", totalErroredTestCases.toString()) + .att("time", testDuration.toString()); + + // Convert the XML structure to a string + const xmlDocString = rootEl.end({ prettyPrint: true }); + + // Write the XML string to the specified path + try { + const resolvedExportPath = path.resolve(reporterJUnitExportPath); + + if (fs.existsSync(resolvedExportPath)) { + info( + INFO(`\nOverwriting the pre-existing path: ${reporterJUnitExportPath}.`) + ); + } + + fs.mkdirSync(path.dirname(resolvedExportPath), { + recursive: true, + }); + + fs.writeFileSync(resolvedExportPath, xmlDocString); + + log( + SUCCESS( + `\nSuccessfully exported the JUnit report to: ${reporterJUnitExportPath}.` + ) + ); + } catch (err) { + const data = err instanceof Error ? err.message : null; + throw error({ + code: "REPORT_EXPORT_FAILED", + data, + path: reporterJUnitExportPath, + }); + } +}; diff --git a/packages/hoppscotch-cli/src/utils/request.ts b/packages/hoppscotch-cli/src/utils/request.ts index 81513bd210..a46cd06945 100644 --- a/packages/hoppscotch-cli/src/utils/request.ts +++ b/packages/hoppscotch-cli/src/utils/request.ts @@ -1,4 +1,9 @@ -import { Environment, HoppCollection, HoppRESTRequest } from "@hoppscotch/data"; +import { + Environment, + HoppCollection, + HoppRESTRequest, + RESTReqSchemaVersion, +} from "@hoppscotch/data"; import axios, { Method } from "axios"; import * as A from "fp-ts/Array"; import * as E from "fp-ts/Either"; @@ -27,8 +32,6 @@ import { getDurationInSeconds, getMetaDataPairs } from "./getters"; import { preRequestScriptRunner } from "./pre-request"; import { getTestScriptParams, hasFailedTestCases, testRunner } from "./test"; -// !NOTE: The `config.supported` checks are temporary until OAuth2 and Multipart Forms are supported - /** * Processes given variable, which includes checking for secret variables * and getting value from system environment @@ -52,10 +55,11 @@ const processVariables = (variable: Environment["variables"][number]) => { * @param envs Global + selected envs used by requests with in collection * @returns Processed envs with each variable processed */ -const processEnvs = (envs: HoppEnvs) => { +const processEnvs = (envs: Partial) => { + // This can take the shape `{ global: undefined, selected: undefined }` when no environment is supplied const processedEnvs = { - global: envs.global.map(processVariables), - selected: envs.selected.map(processVariables), + global: envs.global?.map(processVariables) ?? [], + selected: envs.selected?.map(processVariables) ?? [], }; return processedEnvs; @@ -69,43 +73,20 @@ const processEnvs = (envs: HoppEnvs) => { */ export const createRequest = (req: EffectiveHoppRESTRequest): RequestConfig => { const config: RequestConfig = { - supported: true, displayUrl: req.effectiveFinalDisplayURL, }; + const { finalBody, finalEndpoint, finalHeaders, finalParams } = getRequest; + const reqParams = finalParams(req); const reqHeaders = finalHeaders(req); + config.url = finalEndpoint(req); config.method = req.method as Method; config.params = getMetaDataPairs(reqParams); config.headers = getMetaDataPairs(reqHeaders); - if (req.auth.authActive) { - switch (req.auth.authType) { - case "oauth-2": { - // TODO: OAuth2 Request Parsing - // !NOTE: Temporary `config.supported` check - config.supported = false; - } - default: { - break; - } - } - } - if (req.body.contentType) { - config.headers["Content-Type"] = req.body.contentType; - switch (req.body.contentType) { - case "multipart/form-data": { - // TODO: Parse Multipart Form Data - // !NOTE: Temporary `config.supported` check - config.supported = false; - break; - } - default: { - config.data = finalBody(req); - break; - } - } - } + + config.data = finalBody(req); return config; }; @@ -140,13 +121,6 @@ export const requestRunner = duration: 0, }; - // !NOTE: Temporary `config.supported` check - if ((config as RequestConfig).supported === false) { - status = 501; - runnerResponse.status = status; - runnerResponse.statusText = responseErrors[status]; - } - const end = hrtime(start); const duration = getDurationInSeconds(end); runnerResponse.duration = duration; @@ -165,7 +139,7 @@ export const requestRunner = }; if (axios.isAxiosError(e)) { - runnerResponse.endpoint = e.config.url ?? ""; + runnerResponse.endpoint = e.config?.url ?? ""; if (e.response) { const { data, status, statusText, headers } = e.response; @@ -173,10 +147,6 @@ export const requestRunner = runnerResponse.statusText = statusText; runnerResponse.status = status; runnerResponse.headers = headers; - } else if ((e.config as RequestConfig).supported === false) { - status = 501; - runnerResponse.status = status; - runnerResponse.statusText = responseErrors[status]; } else if (e.request) { return E.left(error({ code: "REQUEST_ERROR", data: E.toError(e) })); } @@ -270,7 +240,9 @@ export const processRequest = // Updating report for errors & current result report.errors.push(preRequestRes.left); - report.result = report.result && false; + + // Ensure, the CLI fails with a non-zero exit code if there are any errors + report.result = false; } else { // Updating effective-request and consuming updated envs after pre-request script execution ({ effectiveRequest, updatedEnvs } = preRequestRes.right); @@ -298,7 +270,9 @@ export const processRequest = if (E.isLeft(requestRunnerRes)) { // Updating report for errors & current result report.errors.push(requestRunnerRes.left); - report.result = report.result && false; + + // Ensure, the CLI fails with a non-zero exit code if there are any errors + report.result = false; printRequestRunner.fail(); } else { @@ -321,7 +295,9 @@ export const processRequest = // Updating report with current errors & result. report.errors.push(testRunnerRes.left); - report.result = report.result && false; + + // Ensure, the CLI fails with a non-zero exit code if there are any errors + report.result = false; } else { const { envs, testsReport, duration } = testRunnerRes.right; const _hasFailedTestCases = hasFailedTestCases(testsReport); @@ -357,7 +333,7 @@ export const preProcessRequest = ( const { headers: parentHeaders, auth: parentAuth } = collection; if (!tempRequest.v) { - tempRequest.v = "1"; + tempRequest.v = RESTReqSchemaVersion; } if (!tempRequest.name) { tempRequest.name = "Untitled Request"; diff --git a/packages/hoppscotch-cli/src/utils/workspace-access.ts b/packages/hoppscotch-cli/src/utils/workspace-access.ts new file mode 100644 index 0000000000..901021c991 --- /dev/null +++ b/packages/hoppscotch-cli/src/utils/workspace-access.ts @@ -0,0 +1,115 @@ +import { + CollectionSchemaVersion, + Environment, + EnvironmentSchemaVersion, + HoppCollection, + HoppRESTAuth, + HoppRESTHeaders, + HoppRESTRequest, +} from "@hoppscotch/data"; + +import { HoppEnvPair } from "../types/request"; + +export interface WorkspaceEnvironment { + id: string; + teamID: string; + name: string; + variables: HoppEnvPair[]; +} + +export interface WorkspaceCollection { + id: string; + data: string | null; + title: string; + parentID: string | null; + folders: WorkspaceCollection[]; + requests: WorkspaceRequest[]; +} + +interface WorkspaceRequest { + id: string; + collectionID: string; + teamID: string; + title: string; + request: string; +} + +/** + * Transforms the incoming list of workspace requests by applying `JSON.parse` to the `request` field. + * It includes the `v` field indicating the schema version, but migration is handled already at the `parseCollectionData()` helper function. + * + * @param {WorkspaceRequest[]} requests - An array of workspace request objects to be transformed. + * @returns {HoppRESTRequest[]} The transformed array of requests conforming to the `HoppRESTRequest` type. + */ +const transformWorkspaceRequests = ( + requests: WorkspaceRequest[] +): HoppRESTRequest[] => requests.map(({ request }) => JSON.parse(request)); + +/** + * Transforms workspace environment data to the `HoppEnvironment` format. + * + * @param {WorkspaceEnvironment} workspaceEnvironment - The workspace environment object to transform. + * @returns {Environment} The transformed environment object conforming to the `Environment` type. + */ +export const transformWorkspaceEnvironment = ( + workspaceEnvironment: WorkspaceEnvironment +): Environment => { + const { teamID, variables, ...rest } = workspaceEnvironment; + + // Add `secret` field if the data conforms to an older schema + const transformedEnvVars = variables.map((variable) => { + if (!("secret" in variable)) { + return { + ...(variable as HoppEnvPair), + secret: false, + } as HoppEnvPair; + } + + return variable; + }); + + // The response doesn't include a way to infer the schema version, so it's set to the latest version + // Any relevant migrations have to be accounted here + return { + v: EnvironmentSchemaVersion, + variables: transformedEnvVars, + ...rest, + }; +}; + +/** + * Transforms workspace collection data to the `HoppCollection` format. + * + * @param {WorkspaceCollection[]} collections - An array of workspace collection objects to be transformed. + * @returns {HoppCollection[]} The transformed array of collections conforming to the `HoppCollection` type. + */ +export const transformWorkspaceCollections = ( + collections: WorkspaceCollection[] +): HoppCollection[] => { + return collections.map((collection) => { + const { id, title, data, requests, folders } = collection; + + const parsedData: { auth?: HoppRESTAuth; headers?: HoppRESTHeaders } = data + ? JSON.parse(data) + : {}; + + const { auth = { authType: "inherit", authActive: true }, headers = [] } = + parsedData; + + const migratedHeaders = headers.map((header) => + header.description ? header : { ...header, description: "" } + ); + + // The response doesn't include a way to infer the schema version, so it's set to the latest version + // Any relevant migrations have to be accounted here + return { + v: CollectionSchemaVersion, + id, + name: title, + folders: transformWorkspaceCollections(folders), + requests: transformWorkspaceRequests(requests), + auth, + headers: migratedHeaders, + }; + }); +}; diff --git a/packages/hoppscotch-cli/tsconfig.json b/packages/hoppscotch-cli/tsconfig.json index 0d8095b075..d360c14f0a 100644 --- a/packages/hoppscotch-cli/tsconfig.json +++ b/packages/hoppscotch-cli/tsconfig.json @@ -10,7 +10,8 @@ "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "composite": true + "composite": true, + "lib": ["ESNext", "DOM"], }, "files": ["package.json"] } diff --git a/packages/hoppscotch-cli/tsup.config.ts b/packages/hoppscotch-cli/tsup.config.ts index 126507ad00..d6e19c1276 100644 --- a/packages/hoppscotch-cli/tsup.config.ts +++ b/packages/hoppscotch-cli/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "tsup"; export default defineConfig({ - entry: [ "./src/index.ts" ], + entry: ["./src/index.ts"], outDir: "./dist/", format: ["esm"], platform: "node", @@ -10,7 +10,7 @@ export default defineConfig({ target: "esnext", skipNodeModulesBundle: false, esbuildOptions(options) { - options.bundle = true + options.bundle = true; }, clean: true, }); diff --git a/packages/hoppscotch-cli/vitest.config.ts b/packages/hoppscotch-cli/vitest.config.ts new file mode 100644 index 0000000000..bac92a0ec4 --- /dev/null +++ b/packages/hoppscotch-cli/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + setupFiles: ["./setupFiles.ts"], + include: ["**/src/__tests__/**/**/*.{test,spec}.ts"], + exclude: [ + "**/node_modules/**", + "**/dist/**", + "**/src/__tests__/functions/**/*.ts", + ], + }, +}); diff --git a/packages/hoppscotch-common/assets/scss/styles.scss b/packages/hoppscotch-common/assets/scss/styles.scss index 258992de71..e2b37d80a5 100644 --- a/packages/hoppscotch-common/assets/scss/styles.scss +++ b/packages/hoppscotch-common/assets/scss/styles.scss @@ -344,26 +344,44 @@ pre.ace_editor { .info-response { color: var(--status-info-color); + &.outlined { + border: 1px solid var(--status-info-color); + } } .success-response { color: var(--status-success-color); + &.outlined { + border: 1px solid var(--status-success-color); + } } .redirect-response { color: var(--status-redirect-color); + &.outlined { + border: 1px solid var(--status-redirect-color); + } } .critical-error-response { color: var(--status-critical-error-color); + &.outlined { + border: 1px solid var(--status-critical-error-color); + } } .server-error-response { color: var(--status-server-error-color); + &.outlined { + border: 1px solid var(--status-server-error-color); + } } .missing-data-response { color: var(--status-missing-data-color); + &.outlined { + border: 1px solid var(--status-missing-data-color); + } } .toasted-container { @@ -560,9 +578,24 @@ details[open] summary .indicator { } } -.env-highlight { - @apply text-accentContrast; +.env-highlight, +.predefined-variable-highlight { + // forcing the text colour to be white inside a higlighted environment variable and predefined variable + @apply text-accentContrast #{!important}; + span { + @apply text-accentContrast #{!important}; + } + // setting the text colour to be visible when it's selected and common item is highlighted + .cm-selectionMatch { + @apply text-secondaryDark #{!important}; + span { + @apply text-secondaryDark #{!important}; + } + } +} + +.env-highlight { &.request-variable-highlight { @apply bg-amber-500; @apply hover:bg-amber-600; @@ -584,6 +617,18 @@ details[open] summary .indicator { } } +.predefined-variable-highlight { + &.predefined-variable-valid { + @apply bg-yellow-500; + @apply hover:bg-yellow-600; + } + + &.predefined-variable-invalid { + @apply hover:bg-red-300; + @apply bg-red-300; + } +} + #nprogress .bar { @apply bg-accent #{!important}; } diff --git a/packages/hoppscotch-common/assets/themes/base-themes.scss b/packages/hoppscotch-common/assets/themes/base-themes.scss index 9bc0a4f308..b5efbcec33 100644 --- a/packages/hoppscotch-common/assets/themes/base-themes.scss +++ b/packages/hoppscotch-common/assets/themes/base-themes.scss @@ -5,7 +5,9 @@ --font-size-tiny: 0.625rem; --line-height-body: 1rem; --upper-primary-sticky-fold: 4.125rem; + --upper-runner-sticky-fold: 4.125rem; --upper-secondary-sticky-fold: 6.188rem; + --upper-runner-sticky-fold: 4.5rem; --upper-tertiary-sticky-fold: 8.25rem; --upper-fourth-sticky-fold: 10.2rem; --upper-mobile-primary-sticky-fold: 6.75rem; diff --git a/packages/hoppscotch-common/locales/af.json b/packages/hoppscotch-common/locales/af.json index b7fa47d9bf..1f741dad09 100644 --- a/packages/hoppscotch-common/locales/af.json +++ b/packages/hoppscotch-common/locales/af.json @@ -24,8 +24,10 @@ "go_back": "Gaan terug", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Etiket", "learn_more": "Leer meer", + "download_here": "Download here", "less": "Less", "more": "Meer", "new": "Nuut", @@ -43,6 +45,7 @@ "search": "Soek", "send": "Stuur", "share": "Share", + "show_secret": "Show secret", "start": "Begin", "starting": "Starting", "stop": "Stop", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Tik 'n opdrag of soek ...", "we_use_cookies": "Ons gebruik koekies", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Wat's nuut?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Rekening bestaan met verskillende geloofsbriewe - Meld aan om beide rekeninge te koppel", "all_sign_in_options": "Alle aanmeldopsies", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Gaan voort met e -pos", "continue_with_github": "Gaan voort met GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Gaan voort met Google", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "Gaan voort met OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Wagwoord", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Teken", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Wysig versameling", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Gee 'n geldige naam vir die versameling", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Kies 'n versameling", "select_location": "Kies ligging", + "details": "Details", "select_team": "Kies 'n span", "team_collections": "Spanversamelings" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Weet u seker dat u van Telemetry wil afskakel?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Is u seker dat u hierdie werkruimte wil sinkroniseer?" + "sync": "Is u seker dat u hierdie werkruimte wil sinkroniseer?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "There are no pending invites for this team", "profile": "Login to view your profile", "protocols": "Protokolle is leeg", + "request_variables": "This request does not have any request variables", "schema": "Koppel aan 'n GraphQL -eindpunt", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Spannaam leeg", "teams": "Spanne is leeg", "tests": "Daar is geen toetse vir hierdie versoek nie", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Kies omgewing", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment updation", "value": "Value", "variable": "Variable", - "variable_list": "Veranderlike lys" + "variables": "Variables", + "variable_list": "Veranderlike lys", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Leë versoeknaam", "f12_details": "(F12 vir meer inligting)", "gql_prettify_invalid_query": "Kon nie 'n ongeldige navraag mooi maak nie, los sintaksisfoute op en probeer weer", @@ -297,6 +337,7 @@ "incorrect_email": "Incorrect email", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "Kon nie 'n ongeldige liggaam mooi maak nie, los json -sintaksisfoute op en probeer weer", "network_error": "There seems to be a network error. Please try again.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Kon nie voorafversoekskrip uitvoer nie", "something_went_wrong": "Iets het verkeerd geloop", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Uitvoer as JSON", "create_secret_gist": "Skep geheime Gist", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gis geskep", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Teken in met GitHub om 'n geheime idee te skep", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Gis geskep" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutasies", "schema": "Skema", "subscriptions": "Inskrywings", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Invoer" + "title": "Invoer", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Rou versoeksliggaam", "rename": "Rename Request", "renamed": "Versoek hernoem", + "request_variables": "Request variables", "run": "Hardloop", "save": "Stoor", "save_as": "Stoor as", @@ -558,6 +614,7 @@ "title": "Versoek", "type": "Soort versoek", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Veranderlikes", "view_my_links": "View my links", "copy_link": "Kopieer skakel" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Stel vrae en kry antwoorde", "github": "Follow us on Github", "shortcuts": "Blaai vinniger deur die app", - "team": "Kontak die span", "title": "Ondersteuning", - "twitter": "volg ons op Twitter" + "twitter": "volg ons op Twitter", + "team": "Kontak die span" }, "tab": { "authorization": "Magtiging", @@ -890,6 +954,9 @@ "query": "Navraag", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Toetse", @@ -918,7 +985,6 @@ "invite_tooltip": "Invite people to this workspace", "invited_to_team": "{owner} invited you to join {team}", "join": "Invitation accepted", - "join_beta": "Sluit aan by die beta -program om toegang tot spanne te kry.", "join_team": "Join {team}", "joined_team": "You have joined {team}", "joined_team_description": "You are now a member of this team", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Spanne", "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Sluit aan by die beta -program om toegang tot spanne te kry." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/ar.json b/packages/hoppscotch-common/locales/ar.json index 4638ca4a7b..44ce8a28ca 100644 --- a/packages/hoppscotch-common/locales/ar.json +++ b/packages/hoppscotch-common/locales/ar.json @@ -1,57 +1,60 @@ { "action": { - "add": "Add", - "autoscroll": "Autoscroll", - "cancel": "الغاء", - "choose_file": "اختيار ملف", - "clear": "امسح", - "clear_all": "امسح الكل", - "clear_history": "Clear all History", - "close": "Close", - "connect": "الاتصال", - "connecting": "Connecting", + "add": "إضافة", + "autoscroll": "التمرير التلقائي", + "cancel": "إلغاء", + "choose_file": "اختر ملف", + "clear": "محو", + "clear_all": "محو الكل", + "clear_history": "محو السجل", + "close": "إغلاق", + "connect": "اتصال", + "connecting": "جارٍ الاتصال", "copy": "نسخ", - "create": "Create", + "create": "إنشاء", "delete": "حذف", "disconnect": "قطع الاتصال", - "dismiss": "رفض", - "dont_save": "Don't save", + "dismiss": "تجاهل", + "dont_save": "لا تحفظ", "download_file": "تحميل الملف", - "drag_to_reorder": "Drag to reorder", - "duplicate": "كرر", - "edit": "يحرر", - "filter": "Filter", - "go_back": "عد", - "go_forward": "Go forward", - "group_by": "Group by", + "drag_to_reorder": "سحب لإعادة الترتيب", + "duplicate": "تكرار", + "edit": "تعديل", + "filter": "تصفية", + "go_back": "رجوع", + "go_forward": "تقدم", + "group_by": "تجميع حسب", + "hide_secret": "إخفاء السر", "label": "ملصق", - "learn_more": "اقرأ أكثر", - "less": "اقل", - "more": "أكثر", + "learn_more": "اقرأ المزيد", + "download_here": "تنزيل هنا", + "less": "أقل", + "more": "المزيد", "new": "جديد", - "no": "رقم", - "open_workspace": "Open workspace", + "no": "لا", + "open_workspace": "افتح مساحة العمل", "paste": "لصق", - "prettify": "جمال", - "properties": "Properties", - "remove": "ازالة", - "rename": "Rename", - "restore": "اعادة", + "prettify": "تجميل", + "properties": "خصائص", + "remove": "إزالة", + "rename": "إعادة تسمية", + "restore": "استعادة", "save": "حفظ", - "scroll_to_bottom": "Scroll to bottom", - "scroll_to_top": "Scroll to top", + "scroll_to_bottom": "التمرير إلى الأسفل", + "scroll_to_top": "التمرير إلى الأعلى", "search": "بحث", - "send": "ارسل", - "share": "Share", + "send": "إرسال", + "share": "مشاركة", + "show_secret": "عرض السر", "start": "ابدأ", - "starting": "Starting", - "stop": "قف", - "to_close": "لإغلاء", - "to_navigate": "للإنتقال", - "to_select": "للإختيار", - "turn_off": "اطفئه", - "turn_on": "شغله", - "undo": "الغاء التحميل", + "starting": "جارٍ البدء", + "stop": "إيقاف", + "to_close": "للإغلاق", + "to_navigate": "للتنقل", + "to_select": "للاختيار", + "turn_off": "إيقاف التشغيل", + "turn_on": "تشغيل", + "undo": "تراجع", "yes": "نعم" }, "add": { @@ -61,56 +64,60 @@ "app": { "chat_with_us": "دردش معنا", "contact_us": "اتصل بنا", - "cookies": "Cookies", - "copy": "انسخ", - "copy_interface_type": "Copy interface type", - "copy_user_id": "Copy User Auth Token", - "developer_option": "Developer options", - "developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.", + "cookies": "ملفات تعريف الارتباط", + "copy": "نسخ", + "copy_interface_type": "نسخ نوع الواجهة", + "copy_user_id": "نسخ رمز تعريف المستخدم", + "developer_option": "خيارات المطور", + "developer_option_description": "أدوات المطور التي تساعد في تطوير وصيانة هوبسكوتش.", "discord": "Discord", "documentation": "توثيق", "github": "GitHub", - "help": "المساعدة والتعليقات ووثائق", + "help": "المساعدة والتعليقات والتوثيق", "home": "الصفحة الرئيسية", "invite": "دعوة", - "invite_description": "في Hoppscotch ، قمنا بتصميم واجهة بسيطة وبديهية لإنشاء وإدارة واجهات برمجة التطبيقات الخاصة بك. Hoppscotch هي أداة تساعدك على بناء واختبار وتوثيق ومشاركة واجهات برمجة التطبيقات الخاصة بك.", - "invite_your_friends": "اعزم أصحابك", + "invite_description": "هوبسكوتش هو نظام بيئي مفتوح المصدر لتطوير API. قمنا بتصميم واجهة بسيطة وبديهية لإنشاء وإدارة API الخاصة بك. هوبسكوتش هي أداة تساعدك على بناء واختبار وتوثيق ومشاركة API الخاصة بك.", + "invite_your_friends": "ادعو أصدقائك", "join_discord_community": "انضم إلى مجتمع Discord الخاص بنا", "keyboard_shortcuts": "اختصارات لوحة المفاتيح", "name": "هوبسكوتش", - "new_version_found": "تم العثور على نسخة جديدة. قم بالتحديث للتحديث.", - "open_in_hoppscotch": "Open in Hoppscotch", - "options": "Options", + "new_version_found": "تم العثور على نسخة جديدة. قم بالتحديث الآن.", + "open_in_hoppscotch": "افتح في هوبسكوتش", + "options": "خيارات", "proxy_privacy_policy": "سياسة خصوصية الوكيل", "reload": "إعادة تحميل", "search": "بحث", - "share": "يشارك", + "share": "مشاركة", "shortcuts": "الاختصارات", - "social_description": "Follow us on social media to stay updated with the latest news, updates and releases.", - "social_links": "Social links", + "social_description": "تابعنا على وسائل التواصل الاجتماعي للبقاء على اطلاع بآخر الأخبار والتحديثات والإصدارات.", + "social_links": "روابط اجتماعية", "spotlight": "أضواء كاشفة", "status": "حالة", - "status_description": "Check the status of the website", + "status_description": "تحقق من حالة الموقع", "terms_and_privacy": "الشروط والخصوصية", "twitter": "Twitter", - "type_a_command_search": "اكتب أمرًا أو ابحث ...", + "type_a_command_search": "اكتب أمرًا أو ابحث...", "we_use_cookies": "نحن نستخدم ملفات تعريف الارتباط", - "whats_new": "ما هو الجديد؟", + "updated_text": "تم تحديث هوبسكوتش إلى الإصدار {version} 🎉", + "whats_new": "ما الجديد؟", + "see_whats_new": "شاهد ما الجديد", "wiki": "ويكي" }, "auth": { "account_exists": "الحساب موجود ببيانات اعتماد مختلفة - تسجيل الدخول لربط كلا الحسابين", "all_sign_in_options": "كل خيارات تسجيل الدخول", + "continue_with_auth_provider": "المواصلة من خلال {provider}", "continue_with_email": "تواصل مع البريد الإلكتروني", - "continue_with_github": "تواصل مع جيثب", - "continue_with_google": "تواصل مع جوجل", - "continue_with_microsoft": "Continue with Microsoft", + "continue_with_github": "المواصلة من خلال جيت هاب", + "continue_with_github_enterprise": "المواصلة من خلال جيت هاب Enterprise", + "continue_with_google": "المواصلة من خلال جوجل", + "continue_with_microsoft": "المواصلة من خلال مايكروسوفت", "continue_with_oidc": "تواصل مع OIDC", "email": "بريد إلكتروني", "logged_out": "تسجيل الخروج", "login": "تسجيل الدخول", "login_success": "تم تسجيل الدخول بنجاح", - "login_to_hoppscotch": "تسجيل الدخول إلى Hoppscotch", + "login_to_hoppscotch": "تسجيل الدخول إلى هوبسكوتش", "logout": "تسجيل خروج", "re_enter_email": "أعد إدخال البريد الإلكتروني", "send_magic_link": "أرسل رابطًا سحريًا", @@ -119,59 +126,82 @@ "we_sent_magic_link_description": "تحقق من بريدك الوارد - لقد أرسلنا بريدًا إلكترونيًا إلى {email}. يحتوي على رابط سحري سيسجل دخولك." }, "authorization": { - "generate_token": "توليد رمز", - "graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", + "generate_token": "توليد رمز الوصول", + "graphql_headers": "تُرسل رؤوس التفويض كجزء من الحمولة إلى `connection_init`", "include_in_url": "تضمين في URL", - "inherited_from": "Inherited from {auth} from Parent Collection {collection} ", + "inherited_from": "موروثة من {auth} من المجموعة الأصلية {collection}", "learn": "تعلم كيف", "oauth": { - "redirect_auth_server_returned_error": "Auth Server returned an error state", - "redirect_auth_token_request_failed": "Request to get the auth token failed", - "redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token", - "redirect_invalid_state": "Invalid State value present in the redirect", - "redirect_no_auth_code": "No Authorization Code present in the redirect", - "redirect_no_client_id": "No Client ID defined", - "redirect_no_client_secret": "No Client Secret Defined", - "redirect_no_code_verifier": "No Code Verifier Defined", - "redirect_no_token_endpoint": "No Token Endpoint Defined", - "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", - "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "redirect_auth_server_returned_error": "عاد خادم Authorization بحالة خطأ", + "redirect_auth_token_request_failed": "فشل الطلب للحصول على رمز Authorization", + "redirect_auth_token_request_invalid_response": "استجابة غير صالحة من الرمز عند طلب رمز Authorization endpoint", + "redirect_invalid_state": "قيمة حالة غير صالحة موجودة في إعادة التوجيه", + "redirect_no_auth_code": "لا يوجد رمز تفويض في إعادة التوجيه", + "redirect_no_client_id": "لم يتم تعريف رقم العميل", + "redirect_no_client_secret": "لم يتم تعريف سر العميل", + "redirect_no_code_verifier": "لم يتم تعريف محقق الشيفرة", + "redirect_no_token_endpoint": "لم يتم تعريف endpoint الرمز", + "something_went_wrong_on_oauth_redirect": "حدث خطأ أثناء إعادة توجيه OAuth", + "something_went_wrong_on_token_generation": "حدث خطأ أثناء توليد الرمز", + "token_generation_oidc_discovery_failed": "فشل توليد الرمز: فشل اكتشاف OpenID Connect", + "grant_type": "نوع المنح", + "grant_type_auth_code": "رمز Authorization", + "token_fetched_successfully": "تم الحصول على الرمز بنجاح", + "token_fetch_failed": "فشل الحصول على الرمز", + "validation_failed": "فشل التحقق، يرجى التحقق من حقول النموذج", + "label_authorization_endpoint": "Authorization endpoint", + "label_client_id": "معرف العميل", + "label_client_secret": "سر العميل", + "label_code_challenge": "تحدي الشيفرة", + "label_code_challenge_method": "طريقة تحدي الشيفرة", + "label_code_verifier": "محقق الشيفرة", + "label_scopes": "النطاقات", + "label_token_endpoint": "endpoint الرمز", + "label_use_pkce": "استخدم PKCE", + "label_implicit": "ضمني", + "label_password": "كلمة المرور", + "label_username": "اسم المستخدم", + "label_auth_code": "رمز Authorization", + "label_client_credentials": "بيانات اعتماد العميل" }, - "pass_key_by": "Pass by", + "pass_key_by": "تمرير بواسطة", + "pass_by_query_params_label": "معلمات الاستعلام", + "pass_by_headers_label": "الرؤوس", "password": "كلمة المرور", - "save_to_inherit": "Please save this request in any collection to inherit the authorization", - "token": "رمز", - "type": "نوع التفويض", + "save_to_inherit": "يرجى حفظ هذا الطلب في أي مجموعة لتوريث Authorization", + "token": "رمز الوصول", + "type": "نوع Authorization", "username": "اسم المستخدم" }, "collection": { "created": "تم إنشاء المجموعة", - "different_parent": "Cannot reorder collection with different parent", + "different_parent": "لا يمكن إعادة ترتيب المجموعة مع والد مختلف", "edit": "تحرير المجموعة", - "import_or_create": "Import or create a collection", + "import_or_create": "استيراد أو إنشاء مجموعة", + "import_collection": "استيراد مجموعة", "invalid_name": "الرجاء تقديم اسم صالح للمجموعة", - "invalid_root_move": "Collection already in the root", - "moved": "Moved Successfully", + "invalid_root_move": "المجموعة موجودة بالفعل في الجذر", + "moved": "تم النقل بنجاح", "my_collections": "مجموعاتي", "name": "مجموعتي الجديدة", - "name_length_insufficient": "اسم المجموعة يجب ان لايقل على 3 رموز", + "name_length_insufficient": "يجب أن لا يقل اسم المجموعة عن 3 رموز", "new": "مجموعة جديدة", - "order_changed": "Collection Order Updated", - "properties": "Collection Properties", - "properties_updated": "Collection Properties Updated", + "order_changed": "تم تحديث ترتيب المجموعة", + "properties": "خصائص المجموعة", + "properties_updated": "تم تحديث خصائص المجموعة", "renamed": "تمت إعادة تسمية المجموعة", - "request_in_use": "Request in use", + "request_in_use": "الطلب قيد الاستخدام", "save_as": "حفظ باسم", - "save_to_collection": "Save to Collection", + "save_to_collection": "حفظ إلى المجموعة", "select": "حدد مجموعة", "select_location": "اختر موقعا", + "details": "تفاصيل", "select_team": "اختر فريقًا", "team_collections": "مجموعات الفريق" }, "confirm": { - "close_unsaved_tab": "Are you sure you want to close this tab?", - "close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", + "close_unsaved_tab": "هل أنت متأكد أنك تريد إغلاق هذه التبويبة؟", + "close_unsaved_tabs": "هل أنت متأكد أنك تريد إغلاق جميع علامات التبويب سيتم فقدان {count} من التبويبات غير المحفوظة.", "exit_team": "هل أنت متأكد أنك تريد مغادرة هذا الفريق؟", "logout": "هل أنت متأكد أنك تريد تسجيل الخروج؟", "remove_collection": "هل أنت متأكد أنك تريد حذف هذه المجموعة نهائيًا؟", @@ -179,58 +209,59 @@ "remove_folder": "هل أنت متأكد أنك تريد حذف هذا المجلد نهائيًا؟", "remove_history": "هل أنت متأكد أنك تريد حذف كل المحفوظات بشكل دائم؟", "remove_request": "هل أنت متأكد أنك تريد حذف هذا الطلب نهائيًا؟", - "remove_shared_request": "Are you sure you want to permanently delete this shared request?", + "remove_shared_request": "هل أنت متأكد أنك تريد حذف هذا الطلب المشترك نهائيًا؟", "remove_team": "هل أنت متأكد أنك تريد حذف هذا الفريق؟", "remove_telemetry": "هل أنت متأكد أنك تريد الانسحاب من القياس عن بعد؟", - "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", - "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "هل أنت متأكد أنك تريد مزامنة مساحة العمل هذه؟" + "request_change": "هل أنت متأكد أنك تريد تجاهل الطلب الحالي؟ سيتم فقدان التغييرات غير المحفوظة.", + "save_unsaved_tab": "هل تريد حفظ التغييرات التي أجريتها في هذه التبويبة؟", + "sync": "هل أنت متأكد أنك تريد مزامنة مساحة العمل هذه؟", + "delete_access_token": "هل أنت متأكد أنك تريد حذف رمز الوصول {tokenLabel}؟" }, "context_menu": { - "add_parameters": "Add to parameters", - "open_request_in_new_tab": "Open request in new tab", - "set_environment_variable": "Set as variable" + "add_parameters": "إضافة إلى parameters", + "open_request_in_new_tab": "فتح الطلب في تبويبة جديدة", + "set_environment_variable": "تعيين كمتغير" }, "cookies": { "modal": { - "cookie_expires": "Expires", - "cookie_name": "Name", - "cookie_path": "Path", - "cookie_string": "Cookie string", - "cookie_value": "Value", - "empty_domain": "Domain is empty", - "empty_domains": "Domain list is empty", - "enter_cookie_string": "Enter cookie string", - "interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.", - "managed_tab": "Managed", - "new_domain_name": "New domain name", - "no_cookies_in_domain": "No cookies set for this domain", - "raw_tab": "Raw", - "set": "Set a cookie" + "cookie_expires": "تنتهي في", + "cookie_name": "الاسم", + "cookie_path": "المسار", + "cookie_string": "نص الكوكي", + "cookie_value": "القيمة", + "empty_domain": "النطاق فارغ", + "empty_domains": "قائمة النطاقات فارغة", + "enter_cookie_string": "أدخل نص الكوكي", + "interceptor_no_support": "الاعتراض المحدد حاليًا لا يدعم الكوكيز. اختر معترضًا مختلفًا وحاول مرة أخرى.", + "managed_tab": "مدار", + "new_domain_name": "اسم نطاق جديد", + "no_cookies_in_domain": "لا توجد كوكيز محددة لهذا النطاق", + "raw_tab": "خام", + "set": "تعيين كوكي" } }, "count": { "header": "رأس {count}", - "message": "الرسالة {count}", + "message": "رسالة {count}", "parameter": "المعلمة {count}", "protocol": "البروتوكول {count}", - "value": "القيمة {count}", + "value": "قيمة {count}", "variable": "متغير {count}" }, "documentation": { - "generate": "توليد الوثائق", - "generate_message": "قم باستيراد أي مجموعة Hoppscotch لإنشاء وثائق API أثناء التنقل." + "generate": "توليد docs", + "generate_message": "قم باستيراد أي مجموعة هوبسكوتش لإنشاء API docs أثناء التنقل." }, "empty": { "authorization": "هذا الطلب لا يستخدم أي إذن", "body": "هذا الطلب ليس له هيئة", "collection": "المجموعة فارغة", "collections": "المجموعات فارغة", - "documentation": "Connect to a GraphQL endpoint to view documentation", - "endpoint": "Endpoint cannot be empty", + "documentation": "اتصل ب GraphQL endpoint لعرض الوثائق", + "endpoint": "لا يمكن أن تكون endpoint فارغة", "environments": "البيئات فارغة", "folder": "مجلد فارغ", - "headers": "لا يحتوي هذا الطلب على أية رؤوس", + "headers": "لا يحتوي هذا الطلب على أية Headers", "history": "التاريخ فارغ", "invites": "قائمة الدعوات فارغة", "members": "الفريق فارغ", @@ -238,116 +269,135 @@ "pending_invites": "لاتوجد اي دعوات معلقة لهذا الفريق", "profile": "سجل الدخول لرؤية فريقك", "protocols": "البروتوكولات فارغة", - "schema": "اتصل بنقطة نهاية GraphQL", - "shared_requests": "Shared requests are empty", - "shared_requests_logout": "Login to view your shared requests or create a new one", - "subscription": "Subscriptions are empty", + "request_variables": "لا يحتوي هذا الطلب على أي متغيرات ", + "schema": " اتصل ب GraphQL endpoint", + "secret_environments": "الأسرار غير متزامنة مع هوبسكوتش", + "shared_requests": "الطلبات المشتركة فارغة", + "shared_requests_logout": "سجل الدخول لعرض طلباتك المشتركة أو لإنشاء طلب جديد", + "subscription": "الاشتراكات فارغة", "team_name": "اسم الفريق فارغ", "teams": "الفرق فارغة", - "tests": "لا توجد اختبارات لهذا الطلب", - "shortcodes": "Shortcodes are empty" + "tests": "لا توجد tests لهذا الطلب", + "access_tokens": "رموز الوصول فارغة", + "shortcodes": "الرموز القصيرة فارغة" }, "environment": { - "add_to_global": "Add to Global", - "added": "Environment addition", - "create_new": "انشاء بيئة جديدة", - "created": "Environment created", - "deleted": "حذف بيئة العمل", - "duplicated": "Environment duplicated", + "add_to_global": "أضف إلى العام", + "added": "تمت إضافة البيئة", + "create_new": "إنشاء بيئة جديدة", + "created": "تم إنشاء البيئة", + "deleted": "تم حذف البيئة", + "duplicated": "تم تكرار البيئة", "edit": "تحرير البيئة", - "empty_variables": "No variables", - "global": "Global", - "global_variables": "Global variables", - "import_or_create": "Import or create a environment", - "invalid_name": "الرجاء تقديم اسم صالح للبيئة", - "list": "Environment variables", - "my_environments": "My Environments", - "name": "Name", - "nested_overflow": "nested environment variables are limited to 10 levels", + "empty_variables": "لا توجد متغيرات", + "global": "عام", + "global_variables": "المتغيرات العامة", + "import_or_create": "استيراد أو إنشاء بيئة", + "invalid_name": "يرجى كنابة اسم صحيح للبيئة", + "list": "متغيرات البيئة", + "my_environments": "البيئات الخاصة بي", + "name": "الاسم", + "nested_overflow": "المتغيرات البيئية المتداخلة محدودة بـ 10 مستويات", "new": "بيئة جديدة", - "no_active_environment": "No active environment", - "no_environment": "لا بيئة", - "no_environment_description": "لم يتم اختيار أي بيئة عمل. اختر ما تريد فعله بالمتغيرات التالية.", - "quick_peek": "Environment Quick Peek", - "replace_with_variable": "Replace with variable", - "scope": "Scope", - "select": "حدد البيئة", - "set": "Set environment", - "set_as_environment": "Set as environment", - "team_environments": "Team Environments", + "no_active_environment": "لا توجد بيئة نشطة", + "no_environment": "لا توجد بيئة", + "no_environment_description": "لم يتم اختيار أي بيئات. اختر ما تريد القيام به مع المتغيرات التالية.", + "quick_peek": "نظرة سريعة على البيئة", + "replace_with_variable": "استبدال بمتغير", + "scope": "نطاق", + "secrets": "الأسرار", + "secret_value": "القيمة السرية", + "select": "اختر البيئة", + "set": "تعيين البيئة", + "set_as_environment": "تعيين كبيئة", + "team_environments": "بيئات العمل", "title": "البيئات", - "updated": "تحديث بيئة العمل", - "value": "Value", - "variable": "Variable", - "variable_list": "قائمة متغيرة" + "updated": "تم تحديث البيئة", + "value": "القيمة", + "variable": "متغير", + "variables": "المتغيرات", + "variable_list": "قائمة المتغيرات", + "properties": "خصائص البيئة", + "details": "التفاصيل" }, "error": { - "authproviders_load_error": "Unable to load auth providers", - "browser_support_sse": "يبدو أن هذا المستعرض لا يدعم أحداث إرسال الخادم.", + "authproviders_load_error": "غير قادر على تحميل مقدمي Authorization", + "browser_support_sse": "يبدو أن هذا المتصفح لا يدعم Server Sent Events.", "check_console_details": "تحقق من سجل وحدة التحكم للحصول على التفاصيل.", - "check_how_to_add_origin": "Check how you can add an origin", - "curl_invalid_format": "لم يتم تنسيق cURL بشكل صحيح", - "danger_zone": "Danger zone", - "delete_account": "Your account is currently an owner in these teams:", - "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "check_how_to_add_origin": "تحقق من كيفية إضافة مصدر", + "curl_invalid_format": "صيغة cURL غير صحيحة", + "danger_zone": "منطقة الخطر", + "delete_account": "حسابك حاليًا هو مالك في مساحات العمل التالية:", + "delete_account_description": "يجب عليك إما إزالة نفسك أو نقل الملكية أو حذف هذه مساحات العمل قبل أن تتمكن من حذف حسابك.", + "empty_profile_name": "لا يمكن أن يكون اسم الملف الشخصي فارغًا", "empty_req_name": "اسم الطلب فارغ", - "f12_details": "(للحصول على تفاصيل F12)", - "gql_prettify_invalid_query": "تعذر تحسين استعلام غير صالح وحل أخطاء بنية الاستعلام وحاول مرة أخرى", - "incomplete_config_urls": "Incomplete configuration URLs", - "incorrect_email": "Incorrect email", - "invalid_link": "Invalid link", - "invalid_link_description": "The link you clicked is invalid or expired.", - "json_parsing_failed": "Invalid JSON", - "json_prettify_invalid_body": "تعذر تجميل جسم غير صالح وحل أخطاء بناء جملة json وحاول مرة أخرى", - "network_error": "There seems to be a network error. Please try again.", - "network_fail": "تعذر إرسال الطلب", - "no_collections_to_export": "No collections to export. Please create a collection to get started.", - "no_duration": "لا مدة", - "no_environments_to_export": "No environments to export. Please create an environment to get started.", - "no_results_found": "No matches found", - "page_not_found": "This page could not be found", - "please_install_extension": "Please install the extension and add origin to the extension.", - "proxy_error": "Proxy error", - "script_fail": "تعذر تنفيذ نص الطلب المسبق", - "something_went_wrong": "هناك خطأ ما", - "test_script_fail": "Could not execute post-request script" + "f12_details": "(F12 للتفاصيل)", + "gql_prettify_invalid_query": "لم يتمكن من تجميل استعلام غير صالح، حل أخطاء بناء الجملة وحاول مرة أخرى", + "incomplete_config_urls": "عناوين URL الخاصة بالتكوين غير مكتملة", + "incorrect_email": "البريد الإلكتروني غير صحيح", + "invalid_link": "الرابط غير صالح", + "invalid_link_description": "الرابط الذي نقرت عليه غير صالح أو منتهي الصلاحية.", + "invalid_embed_link": "التضمين غير موجود أو غير صالح.", + "json_parsing_failed": "JSON غير صالح", + "json_prettify_invalid_body": "لم يتمكن من تجميل نص غير صالح، حل أخطاء بناء الجملة في JSON وحاول مرة أخرى", + "network_error": "يبدو أن هناك خطأ في الشبكة. حاول مرة أخرى.", + "network_fail": "لم يتمكن من إرسال الطلب", + "no_collections_to_export": "لا توجد مجموعات للتصدير. يرجى إنشاء مجموعة للبدء.", + "no_duration": "لا يوجد مدة", + "no_environments_to_export": "لا توجد بيئات للتصدير. يرجى إنشاء بيئة للبدء.", + "no_results_found": "لم يتم العثور على نتائج", + "page_not_found": "لا يمكن العثور على هذه الصفحة", + "please_install_extension": "يرجى تثبيت الإضافة وإضافة المصدر إلى الإضافة.", + "proxy_error": "خطأ في الوكيل", + "same_profile_name": "اسم الملف الشخصي المحدث هو نفسه اسم الملف الشخصي الحالي", + "script_fail": "لم يتمكن من تنفيذ نص الطلب المسبق", + "something_went_wrong": "حدث خطأ ما", + "test_script_fail": "لم يتمكن من تنفيذ نص ما بعد الطلب", + "reading_files": "حدث خطأ أثناء قراءة واحد أو أكثر من الملفات.", + "fetching_access_tokens_list": "حدث خطأ أثناء جلب قائمة الرموز", + "generate_access_token": "حدث خطأ أثناء توليد رمز الوصول", + "delete_access_token": "حدث خطأ أثناء حذف رمز الوصول" }, "export": { - "as_json": "تصدير بتنسيق JSON", - "create_secret_gist": "إنشاء جوهر سري", - "failed": "Something went wrong while exporting", - "gist_created": "خلقت الجست", - "require_github": "تسجيل الدخول باستخدام GitHub لإنشاء جوهر سري", - "title": "Export" + "as_json": "تصدير كملف JSON", + "create_secret_gist": "إنشاء Gist سري", + "create_secret_gist_tooltip_text": "تصدير كـ Gist سري", + "failed": "حدث خطأ أثناء التصدير", + "secret_gist_success": "تم التصدير بنجاح كـ Gist سري", + "require_github": "تسجيل الدخول باستخدام GitHub لإنشاء Gist سري", + "title": "تصدير", + "success": "تم التصدير بنجاح", + "gist_created": "تم إنشاء Gist" }, "filter": { - "all": "All", - "none": "None", - "starred": "Starred" + "all": "الكل", + "none": "لا شيء", + "starred": "المفضلة" }, "folder": { "created": "تم إنشاء المجلد", "edit": "تحرير المجلد", - "invalid_name": "الرجاء توفير اسم للمجلد", - "name_length_insufficient": "اسم المجلد يحب ان يكون على اقل 3 رموز", - "new": "ملف جديد", + "invalid_name": "يرجى تقديم اسم للمجلد", + "name_length_insufficient": "يجب أن يتكون اسم المجلد من 3 أحرف على الأقل", + "new": "مجلد جديد", "renamed": "تمت إعادة تسمية المجلد" }, "graphql": { - "connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?", - "connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is", - "connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is", - "mutations": "الطفرات", - "schema": "مخطط", + "connection_switch_confirm": "هل تريد الاتصال بنقطة النهاية الأخيرة لـ GraphQL؟", + "connection_switch_new_url": "التبديل إلى علامة تبويب أخرى سيفصلك عن الاتصال النشط بـ GraphQL. عنوان URL للاتصال الجديد هو", + "connection_switch_url": "أنت متصل بنقطة نهاية GraphQL وعنوان URL للاتصال هو", + "mutations": "التغييرات", + "schema": "المخطط", "subscriptions": "الاشتراكات", - "switch_connection": "Switch connection" + "switch_connection": "تبديل الاتصال", + "url_placeholder": "أدخل عنوان URL لنقطة نهاية GraphQL" }, "graphql_collections": { - "title": "GraphQL Collections" + "title": "مجموعات GraphQL" }, "group": { - "time": "Time", - "url": "URL" + "time": "الوقت", + "url": "الرابط" }, "header": { "install_pwa": "تثبيت التطبيق", @@ -356,123 +406,128 @@ }, "helpers": { "authorization": "سيتم إنشاء رأس التفويض تلقائيًا عند إرسال الطلب.", - "collection_properties_authorization": " This authorization will be set for every request in this collection.", - "collection_properties_header": "This header will be set for every request in this collection.", + "collection_properties_authorization": "سيتم تعيين هذا التفويض لكل طلب في هذه المجموعة.", + "collection_properties_header": "سيتم تعيين هذا الرأس لكل طلب في هذه المجموعة.", "generate_documentation_first": "قم بإنشاء الوثائق أولاً", - "network_fail": "تعذر الوصول إلى نقطة نهاية API. تحقق من اتصالك بالشبكة وحاول مرة أخرى.", - "offline": "يبدو أنك غير متصل بالإنترنت. قد لا تكون البيانات الموجودة في مساحة العمل هذه محدثة.", - "offline_short": "يبدو أنك غير متصل بالإنترنت.", - "post_request_tests": "تتم كتابة نصوص الاختبار بلغة JavaScript ، ويتم تشغيلها بعد تلقي الاستجابة.", - "pre_request_script": "تتم كتابة البرامج النصية للطلب المسبق بلغة JavaScript ، ويتم تشغيلها قبل إرسال الطلب.", - "script_fail": "يبدو أن هناك خللًا في نص الطلب المسبق. تحقق من الخطأ أدناه وقم بإصلاح البرنامج النصي وفقًا لذلك.", - "test_script_fail": "There seems to be an error with test script. Please fix the errors and run tests again", - "tests": "اكتب نص اختبار لأتمتة تصحيح الأخطاء." + "network_fail": "تعذر الوصول إلى نقطة النهاية الخاصة بـ API. تحقق من اتصال الشبكة الخاص بك أو اختر Interceptor مختلفًا وحاول مرة أخرى.", + "offline": "أنت تستخدم Hoppscotch دون اتصال. سيتم مزامنة التحديثات عندما تكون متصلاً بالإنترنت، بناءً على إعدادات مساحة العمل.", + "offline_short": "أنت تستخدم Hoppscotch دون اتصال.", + "post_request_tests": "يتم كتابة نصوص الاختبار بلغة JavaScript، ويتم تشغيلها بعد استلام الاستجابة.", + "pre_request_script": "يتم كتابة نصوص ما قبل الطلب بلغة JavaScript، ويتم تشغيلها قبل إرسال الطلب.", + "script_fail": "يبدو أن هناك خللاً في نص ما قبل الطلب. تحقق من الخطأ أدناه وقم بإصلاح النص وفقًا لذلك.", + "test_script_fail": "يبدو أن هناك خطأ في نص الاختبار. يرجى إصلاح الأخطاء وإعادة تشغيل الاختبارات مرة أخرى", + "tests": "اكتب نص اختبار لأتمتة التصحيح." }, "hide": { - "collection": "Collapse Collection Panel", + "collection": "طي لوحة المجموعة", "more": "إخفاء المزيد", "preview": "إخفاء المعاينة", - "sidebar": "اخفاء الشريط الجانبي" + "sidebar": "طي الشريط الجانبي" }, "import": { - "collections": "مجموعات الاستيراد", + "collections": "استيراد المجموعات", "curl": "استيراد cURL", - "environments_from_gist": "Import From Gist", - "environments_from_gist_description": "Import Hoppscotch Environments From Gist", - "failed": "فشل الاستيراد", - "from_file": "Import from File", - "from_gist": "الاستيراد من Gist", - "from_gist_description": "استيراد من Gist URL", + "environments_from_gist": "استيراد من Gist", + "environments_from_gist_description": "استيراد بيئات Hoppscotch من Gist", + "failed": "حدث خطأ أثناء الاستيراد: التنسيق غير معترف به", + "from_file": "استيراد من ملف", + "from_gist": "استيراد من Gist", + "from_gist_description": "استيراد من رابط Gist", "from_insomnia": "استيراد من Insomnia", "from_insomnia_description": "استيراد من مجموعة Insomnia", - "from_json": "Import from Hoppscotch", - "from_json_description": "استيراد من ملف مجموعة Hoppscotch", - "from_my_collections": "الاستيراد من \"مجموعاتي\"", - "from_my_collections_description": "استيراد من ملف مجموعاتي", + "from_json": "استيراد من Hoppscotch", + "from_json_description": "استيراد من ملف مجموعة Hoppscotch", + "from_my_collections": "استيراد من مجموعاتي الشخصية", + "from_my_collections_description": "استيراد من ملف مجموعاتي الشخصية", "from_openapi": "استيراد من OpenAPI", - "from_openapi_description": "استيراد من ملف (YML/JSON) ل OpenAPI", + "from_openapi_description": "استيراد من ملف مواصفات OpenAPI (YML/JSON)", "from_postman": "استيراد من Postman", "from_postman_description": "استيراد من مجموعة Postman", - "from_url": "استيراد من رابط", - "gist_url": "أدخل عنوان URL لـ Gist", - "gql_collections_from_gist_description": "Import GraphQL Collections From Gist", - "hoppscotch_environment": "Hoppscotch Environment", - "hoppscotch_environment_description": "Import Hoppscotch Environment JSON file", - "import_from_url_invalid_fetch": "Couldn't get data from the url", - "import_from_url_invalid_file_format": "Error while importing collections", - "import_from_url_invalid_type": "Unsupported type. accepted values are 'hoppscotch', 'openapi', 'postman', 'insomnia'", - "import_from_url_success": "Collections Imported", - "insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file", - "json_description": "استيراد مجموعة من ملفHoppscotch Collections JSON file", - "postman_environment": "Postman Environment", - "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "يستورد" + "from_url": "استيراد من رابط URL", + "gist_url": "أدخل رابط Gist", + "gql_collections_from_gist_description": "استيراد مجموعات GraphQL من Gist", + "hoppscotch_environment": "بيئة Hoppscotch", + "hoppscotch_environment_description": "استيراد ملف JSON لبيئة Hoppscotch", + "import_from_url_invalid_fetch": "تعذر الحصول على البيانات من الرابط", + "import_from_url_invalid_file_format": "حدث خطأ أثناء استيراد المجموعات", + "import_from_url_invalid_type": "نوع غير مدعوم. القيم المقبولة هي 'hoppscotch'، 'openapi'، 'postman'، 'insomnia'", + "import_from_url_success": "تم استيراد المجموعات بنجاح", + "insomnia_environment_description": "استيراد بيئة Insomnia من ملف JSON/YAML", + "json_description": "استيراد المجموعات من ملف JSON الخاص بـ Hoppscotch", + "postman_environment": "بيئة Postman", + "postman_environment_description": "استيراد بيئة Postman من ملف JSON", + "title": "استيراد", + "file_size_limit_exceeded_warning_multiple_files": "الملفات المختارة تتجاوز الحد الموصى به وهو 10MB. سيتم استيراد الملفات {files} الأولى المختارة فقط", + "file_size_limit_exceeded_warning_single_file": "الملف المختار حاليًا يتجاوز الحد الموصى به وهو 10MB. يرجى اختيار ملف آخر.", + "success": "تم الاستيراد بنجاح" }, "inspections": { - "description": "Inspect possible errors", + "description": "فحص الأخطاء المحتملة", "environment": { - "add_environment": "Add to Environment", - "not_found": "Environment variable “{environment}” not found." + "add_environment": "إضافة إلى البيئة", + "add_environment_value": "إضافة قيمة", + "empty_value": "قيمة البيئة فارغة للمتغير '{variable}'", + "not_found": "لم يتم العثور على متغير البيئة “{environment}”." }, "header": { - "cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." + "cookie": "لا يسمح المتصفح لـ هوبسكوتش بتعيين عنوان كوكي. بينما نعمل على تطبيق هوبسكوتش لسطح المكتب (قريبًا)، يرجى استخدام عنوان Authorization بدلاً من ذلك." }, "response": { - "401_error": "Please check your authentication credentials.", - "404_error": "Please check your request URL and method type.", - "cors_error": "Please check your Cross-Origin Resource Sharing configuration.", - "default_error": "Please check your request.", - "network_error": "Please check your network connection." + "401_error": "يرجى التحقق من بيانات اعتماد المصادقة الخاصة بك.", + "404_error": "يرجى التحقق من عنوان URL للطلب ونوع الطريقة.", + "cors_error": "يرجى التحقق من تكوين مشاركة الموارد عبر المواقع.", + "default_error": "يرجى التحقق من طلبك.", + "network_error": "يرجى التحقق من اتصال الشبكة الخاص بك." }, "title": "Inspector", "url": { - "extension_not_installed": "Extension not installed.", - "extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", - "extention_enable_action": "Enable Browser Extension", - "extention_not_enabled": "Extension not enabled." + "extension_not_installed": "الاضافة غير مثبتة", + "extension_unknown_origin": "تأكد من أنك أضفت أصل نقطة نهاية API إلى قائمة ملحق متصفح هوبسكوتش.", + "extention_enable_action": "تفعيل الاضافة", + "extention_not_enabled": "الاضافة غير مفعلة" } }, "layout": { - "collapse_collection": "Collapse or Expand Collections", - "collapse_sidebar": "Collapse or Expand the sidebar", - "column": "تصيم عمودي", - "name": "Layout", - "row": "تصميم افقي" + "collapse_collection": "طي أو توسيع المجموعات", + "collapse_sidebar": "طي أو توسيع الشريط الجانبي", + "column": "تصميم عمودي", + "name": "التصميم", + "row": "تصميم أفقي" }, "modal": { - "close_unsaved_tab": "You have unsaved changes", + "close_unsaved_tab": "لديك تغييرات غير محفوظة", "collections": "المجموعات", - "confirm": "يتأكد", - "customize_request": "Customize Request", + "confirm": "تأكيد", + "customize_request": "تخصيص الطلب", "edit_request": "تحرير الطلب", - "import_export": "استيراد و تصدير", - "share_request": "Share Request" + "import_export": "استيراد وتصدير", + "share_request": "مشاركة الطلب" }, "mqtt": { - "already_subscribed": "You are already subscribed to this topic.", - "clean_session": "Clean Session", - "clear_input": "Clear input", - "clear_input_on_send": "Clear input on send", - "client_id": "Client ID", - "color": "Pick a color", + "already_subscribed": "أنت مشترك بالفعل في هذا الموضوع.", + "clean_session": "جلسة نظيفة", + "clear_input": "مسح المدخلات", + "clear_input_on_send": "مسح المدخلات عند الإرسال", + "client_id": "رقم العميل", + "color": "اختر لونًا", "communication": "تواصل", - "connection_config": "Connection Config", - "connection_not_authorized": "This MQTT connection does not use any authentication.", - "invalid_topic": "Please provide a topic for the subscription", - "keep_alive": "Keep Alive", + "connection_config": "تكوين الاتصال", + "connection_not_authorized": "هذا الاتصال بـ MQTT لا يستخدم أي مصادقة.", + "invalid_topic": "يرجى تقديم موضوع للاشتراك", + "keep_alive": "الحفاظ على الاتصال", "log": "سجل", - "lw_message": "Last-Will Message", - "lw_qos": "Last-Will QoS", - "lw_retain": "Last-Will Retain", - "lw_topic": "Last-Will Topic", + "lw_message": "رسالة آخر مشيئة", + "lw_qos": "آخر مشيئة QoS", + "lw_retain": "آخر مشيئة احتفاظ", + "lw_topic": "موضوع آخر مشيئة", "message": "رسالة", - "new": "New Subscription", - "not_connected": "Please start a MQTT connection first.", - "publish": "ينشر", + "new": "اشتراك جديد", + "not_connected": "يرجى بدء اتصال MQTT أولاً.", + "publish": "نشر", "qos": "QoS", "ssl": "SSL", - "subscribe": "يشترك", - "topic": "عنوان", + "subscribe": "اشتراك", + "topic": "موضوع", "topic_name": "اسم الموضوع", "topic_title": "نشر / اشتراك الموضوع", "unsubscribe": "إلغاء الاشتراك", @@ -494,7 +549,7 @@ }, "profile": { "app_settings": "إعدادات التطبيق", - "default_hopp_displayname": "Unnamed User", + "default_hopp_displayname": "مستخدم بلا اسم", "editor": "محرر", "editor_description": "المحررين يمكنهم اضافة و تعديل و حذف الطلبات.", "email_verification_mail": "تم إرسال رابط التحقق إلى بريدك الإلكتروني. الرجاء الضغط على الرابط لتأكيد بريدك الإلكتروني.", @@ -512,83 +567,85 @@ }, "request": { "added": "تمت إضافة الطلب", - "authorization": "تفويض", - "body": "طلب الهيئة", + "authorization": "Authorization", + "body": "Body", "choose_language": "اختر اللغة", - "content_type": "نوع المحتوى", + "content_type": "Content-Type", "content_type_titles": { - "others": "Others", + "others": "أخرى", "structured": "Structured", - "text": "Text" + "text": "نص" }, - "different_collection": "Cannot reorder requests from different collections", - "duplicated": "Request duplicated", + "different_collection": "لا يمكن إعادة ترتيب الطلبات من مجموعات مختلفة", + "duplicated": "تم تكرار الطلب", "duration": "مدة", "enter_curl": "أدخل cURL", - "generate_code": "إنشاء التعليمات البرمجية", + "generate_code": "إنشاء الكود", "generated_code": "رمز تم إنشاؤه", - "go_to_authorization_tab": "Go to Authorization tab", - "go_to_body_tab": "Go to Body tab", - "header_list": "قائمة الرأس", + "go_to_authorization_tab": "الانتقال إلى علامة التبويب Authorization", + "go_to_body_tab": "الانتقال إلى علامة التبويب Body", + "header_list": "قائمة Header", "invalid_name": "يرجى تقديم اسم للطلب", "method": "طريقة", - "moved": "Request moved", + "moved": "تم نقل الطلب", "name": "اسم الطلب", - "new": "New Request", - "order_changed": "Request Order Updated", + "new": "طلب جديد", + "order_changed": "تم تحديث ترتيب الطلبات", "override": "Override", "override_help": "Set Content-Type in Headers", "overriden": "Overridden", - "parameter_list": "معلمات الاستعلام", - "parameters": "حدود", + "parameter_list": "قائمة Parameters", + "parameters": "Parameters", "path": "طريق", - "payload": "الحمولة", - "query": "استفسار", + "payload": "Payload", + "query": "Query", "raw_body": "نص طلب خام", - "rename": "Rename Request", + "rename": "إعادة تسمية الطلب", "renamed": "تمت إعادة تسمية الطلب", - "run": "يركض", - "save": "يحفظ", + "request_variables": "متغيرات الطلب", + "run": "تشغيل", + "save": "حفظ", "save_as": "حفظ باسم", "saved": "تم حفظ الطلب", - "share": "يشارك", - "share_description": "Share Hoppscotch with your friends", - "share_request": "Share Request", - "stop": "Stop", + "share": "مشاركة", + "share_description": "شارك هوبسكوتش مع أصدقائك", + "share_request": "مشاركة الطلب", + "stop": "إيقاف", "title": "طلب", "type": "نوع الطلب", "url": "URL", + "url_placeholder": "أدخل URL أو الصق أمر cURL", "variables": "المتغيرات", - "view_my_links": "View my links", - "copy_link": "نسخ الوصلة" + "view_my_links": "عرض روابطى", + "copy_link": "نسخ الرابط" }, "response": { - "audio": "Audio", - "body": "هيئة الاستجابة", + "audio": "صوت", + "body": "Body", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)", - "headers": "الرؤوس", - "html": "لغة البرمجة", + "headers": "Headers", + "html": "HTML", "image": "صورة", - "json": "جسون", + "json": "JSON", "pdf": "PDF", "preview_html": "معاينة HTML", - "raw": "الخام", + "raw": "خام", "size": "مقاس", "status": "حالة", "time": "وقت", - "title": "إجابة", - "video": "Video", + "title": "الاستجابة", + "video": "فيديو", "waiting_for_connection": "في انتظار الاتصال", "xml": "XML" }, "settings": { "accent_color": "لون التمييز", "account": "حساب", - "account_deleted": "Your account has been deleted", + "account_deleted": "تم حذف الحساب", "account_description": "تخصيص إعدادات حسابك.", "account_email_description": "عنوان بريدك الإلكتروني الأساسي.", "account_name_description": "هذا هو اسم العرض الخاص بك.", - "additional": "Additional Settings", + "additional": "المزيد من الإعدادات", "background": "خلفية", "black_mode": "أسود", "choose_language": "اختر اللغة", @@ -600,65 +657,65 @@ "experiments_notice": "هذه مجموعة من التجارب التي نعمل عليها والتي قد تكون مفيدة ، أو ممتعة ، أو كليهما ، أو لا شيء. إنها ليست نهائية وقد لا تكون مستقرة ، لذلك إذا حدث شيء غريب للغاية ، فلا داعي للذعر. فقط قم بإيقاف تشغيل الشيء. النكات جانبا،", "extension_ver_not_reported": "لم يبلغ عنها", "extension_version": "نسخة التمديد", - "extensions": "ملحقات", - "extensions_use_toggle": "استخدم امتداد المتصفح لإرسال الطلبات (إن وجدت)", - "follow": "Follow Us", + "extensions": "الاضافات", + "extensions_use_toggle": "استخدم اضافة المتصفح لإرسال الطلبات (إن وجدت)", + "follow": "تابعنا", "interceptor": "المعترض", "interceptor_description": "البرامج الوسيطة بين التطبيق وواجهات برمجة التطبيقات.", - "language": "لغة", + "language": "اللغة", "light_mode": "الوضع الفاتح", - "official_proxy_hosting": "يستضيف Hoppscotch الوكيل الرسمي.", - "profile": "Profile", + "official_proxy_hosting": "يستضيف هوبسكوتش الوكيل الرسمي.", + "profile": "الملف الشخصي", "profile_description": "قم بتحديث بيانات ملفك الشخصي", "profile_email": "البريد الإلكتروني", - "profile_name": "الإسم", + "profile_name": "الاسم", "proxy": "الوكيل", "proxy_url": "وكيل URL", "proxy_use_toggle": "استخدم البرنامج الوسيط الوكيل لإرسال الطلبات", "read_the": "إقرأ ال", - "reset_default": "إعادة تعيين إلى الافتراضي", - "short_codes": "Short codes", - "short_codes_description": "Short codes which were created by you.", - "sidebar_on_left": "Sidebar on left", + "reset_default": "الرجوع إلى الإعدادات الافتراضية", + "short_codes": "الرموز القصيرة", + "short_codes_description": "الرموز القصيرة التي أنشأتها.", + "sidebar_on_left": "الشريط الجانبي على اليسار", "sync": "تزامن", "sync_collections": "المجموعات", "sync_description": "تتم مزامنة هذه الإعدادات مع السحابة.", "sync_environments": "البيئات", "sync_history": "تاريخ", - "system_mode": "نظام", + "system_mode": "وضع النظام", "telemetry": "القياس عن بعد", "telemetry_helps_us": "يساعدنا القياس عن بعد على تخصيص عملياتنا وتقديم أفضل تجربة لك.", "theme": "سمة", "theme_description": "تخصيص موضوع التطبيق الخاص بك.", "use_experimental_url_bar": "استخدم شريط URL التجريبي مع تمييز البيئة", "user": "المستخدم", - "verified_email": "Verified email", + "verified_email": "بريد إلكتروني موثق", "verify_email": "تأكيد البريد الإلكتروني" }, "shared_requests": { - "button": "Button", - "button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.", - "copy_html": "Copy HTML", - "copy_link": "Copy Link", - "copy_markdown": "Copy Markdown", - "creating_widget": "Creating widget", - "customize": "Customize", - "deleted": "Shared request deleted", - "description": "Select a widget, you can change and customize this later", - "embed": "Embed", - "embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.", - "link": "Link", - "link_info": "Create a shareable link to share with anyone on the internet with view access.", - "modified": "Shared request modified", - "not_found": "Shared request not found", - "open_new_tab": "Open in new tab", - "preview": "Preview", - "run_in_hoppscotch": "Run in Hoppscotch", + "button": "زر", + "button_info": "أنشئ زر 'تشغيل في هوبسكوتش' لموقعك الإلكتروني أو مدونتك أو ملف README.", + "copy_html": "نسخ HTML", + "copy_link": "نسخ الرابط", + "copy_markdown": "نسخ Markdown", + "creating_widget": "إنشاء الواجهة", + "customize": "تخصيص", + "deleted": "تم حذف الطلب المشترك", + "description": "اختر واجهة، يمكنك تغييرها وتخصيصها لاحقًا", + "embed": "تضمين", + "embed_info": "أضف 'ملعب API في هوبسكوتش' مصغر إلى موقعك الإلكتروني أو مدونتك أو مستنداتك.", + "link": "رابط", + "link_info": "أنشئ رابطًا قابلًا للمشاركة لتشاركه مع أي شخص على الإنترنت مع إمكانية العرض.", + "modified": "تم تعديل الطلب المشترك", + "not_found": "لم يتم العثور على الطلب المشترك", + "open_new_tab": "فتح في تبويبة جديدة", + "preview": "معاينة", + "run_in_hoppscotch": "تشغيل في هوبسكوتش", "theme": { - "dark": "Dark", - "light": "Light", - "system": "System", - "title": "Theme" + "dark": "داكن", + "light": "فاتح", + "system": "الوضع الإفتراضي للنظام", + "title": "الموضوع" } }, "shortcut": { @@ -670,7 +727,7 @@ "title": "عام" }, "miscellaneous": { - "invite": "ادعُ الناس إلى Hoppscotch", + "invite": "دعوة الناس إلى هوبسكوتش", "title": "متفرقات" }, "navigation": { @@ -678,7 +735,7 @@ "documentation": "انتقل إلى صفحة التوثيق", "forward": "انتقل إلى الصفحة التالية", "graphql": "انتقل إلى صفحة GraphQL", - "profile": "Go to Profile page", + "profile": "انتقل إلى صفحة الملف الشخصي", "realtime": "انتقل إلى صفحة الوقت الفعلي", "rest": "انتقل إلى صفحة REST", "settings": "انتقل إلى صفحة الإعدادات", @@ -686,10 +743,10 @@ }, "others": { "prettify": "Prettify Editor's Content", - "title": "Others" + "title": "أخرى" }, "request": { - "delete_method": "حدد طريقة الحذف", + "delete_method": "حدد طريقة DELETE", "get_method": "حدد طريقة GET", "head_method": "حدد طريقة HEAD", "import_curl": "Import cURL", @@ -698,125 +755,132 @@ "post_method": "حدد طريقة POST", "previous_method": "حدد الطريقة السابقة", "put_method": "حدد طريقة PUT", - "rename": "Rename Request", + "rename": "إعادة تسمية الطلب", "reset_request": "طلب إعادة التعيين", - "save_request": "Save Request", + "save_request": "حفظ الطلب", "save_to_collections": "حفظ في المجموعات", "send_request": "ارسل طلب", - "share_request": "Share Request", - "show_code": "Generate code snippet", + "share_request": "مشاركة الطلب", + "show_code": "توليد الكود", "title": "طلب", - "copy_request_link": "نسخ ارتباط الطلب" + "copy_request_link": "نسخ رابط الطلب" }, "response": { - "copy": "Copy response to clipboard", - "download": "Download response as file", - "title": "Response" + "copy": "نسخ الاستجابة إلى الحافظة", + "download": "تنزيل الاستجابة كملف", + "title": "الاستجابة" }, "theme": { "black": "انتقل الى الوضع الأسود", "dark": "انتقل الى الوضع الليلي", "light": "انتقل الى الوضح الفاتح", "system": "انتقل الى الوضع الإفتراضي للنظام", - "title": "Theme" + "title": "السمة" } }, "show": { - "code": "أظهر الكود", - "collection": "Expand Collection Panel", - "more": "أظهر المزيد", + "code": "عرض الكود", + "collection": "توسيع لوحة المجموعة", + "more": "عرض المزيد", "sidebar": "عرض الشريط الجانبي" }, "socketio": { "communication": "تواصل", - "connection_not_authorized": "This SocketIO connection does not use any authentication.", + "connection_not_authorized": "اتصال SocketIO هذا لا يستخدم أي مصادقة.", "event_name": "اسم الحدث", "events": "الأحداث", "log": "سجل", "url": "URL" }, "spotlight": { - "change_language": "Change Language", + "change_language": "تغيير اللغة", "environments": { - "delete": "Delete current environment", - "duplicate": "Duplicate current environment", - "duplicate_global": "Duplicate global environment", - "edit": "Edit current environment", - "edit_global": "Edit global environment", - "new": "Create new environment", - "new_variable": "Create a new environment variable", - "title": "Environments" + "delete": "حذف البيئة الحالية", + "duplicate": "تكرار البيئة الحالية", + "duplicate_global": "تكرار البيئة العامة", + "edit": "تحرير البيئة الحالية", + "edit_global": "تحرير البيئة العامة", + "new": "إنشاء بيئة جديدة", + "new_variable": "إنشاء متغير بيئة جديد", + "title": "البيئات" }, "general": { - "chat": "Chat with support", - "help_menu": "Help and support", - "open_docs": "Read Documentation", - "open_github": "Open GitHub repository", - "open_keybindings": "Keyboard shortcuts", - "social": "Social", - "title": "General" + "chat": "الدردشة مع الدعم", + "help_menu": "المساعدة والدعم", + "open_docs": "قراءة المستندات", + "open_github": "فتح مستودع جيت هاب", + "open_keybindings": "اختصارات لوحة المفاتيح", + "social": "وسائل التواصل الاجتماعي", + "title": "عام" }, "graphql": { - "connect": "Connect to server", - "disconnect": "Disconnect from server" + "connect": "الاتصال بالخادم", + "disconnect": "انهاء الاتصال بالخادم" }, "miscellaneous": { - "invite": "Invite your friends to Hoppscotch", - "title": "Miscellaneous" + "invite": "دعوة أصدقائك إلى هوبسكوتش", + "title": "متنوع" }, "request": { - "save_as_new": "Save as new request", - "select_method": "Select method", - "switch_to": "Switch to", - "tab_authorization": "Authorization tab", - "tab_body": "Body tab", - "tab_headers": "Headers tab", - "tab_parameters": "Parameters tab", - "tab_pre_request_script": "Pre-request script tab", - "tab_query": "Query tab", - "tab_tests": "Tests tab", - "tab_variables": "Variables tab" + "save_as_new": "حفظ كطلب جديد", + "select_method": "اختيار الطريقة", + "switch_to": "الانتقال إلى", + "tab_authorization": "تبويب Authorization", + "tab_body": "تبويب Body", + "tab_headers": "تبويب Headers", + "tab_parameters": "تبويب Parameters", + "tab_pre_request_script": "تبويب السكربت قبل الطلب", + "tab_query": "تبويب Query", + "tab_tests": "تبويب Tests", + "tab_variables": "تبويب Variables" }, "response": { - "copy": "Copy response", - "download": "Download response as file", - "title": "Response" + "copy": "نسخ الاستجابة", + "download": "تحميل الاستجابة كملف", + "title": "الاستجابة" }, "section": { - "interceptor": "Interceptor", - "interface": "Interface", - "theme": "Theme", - "user": "User" + "interceptor": "القاطع", + "interface": "الواجهة", + "theme": "السمة", + "user": "المستخدم" }, "settings": { - "change_interceptor": "Change Interceptor", - "change_language": "Change Language", + "change_interceptor": "تغيير القاطع", + "change_language": "تغيير اللغة", "theme": { - "black": "Black", - "dark": "Dark", - "light": "Light", - "system": "System preference" + "black": "أسود", + "dark": "داكن", + "light": "فاتح", + "system": "الوضع الإفتراضي للنظام" } }, "tab": { - "close_current": "Close current tab", - "close_others": "Close all other tabs", - "duplicate": "Duplicate current tab", - "new_tab": "Open a new tab", - "title": "Tabs" + "close_current": "إغلاق علامة التبويب الحالية", + "close_others": "إغلاق كل علامات التبويب الأخرى", + "duplicate": "تكرار علامة التبويب الحالية", + "new_tab": "فتح تبويب جديد", + "title": "علامات التبويب" }, "workspace": { - "delete": "Delete current team", - "edit": "Edit current team", - "invite": "Invite people to team", - "new": "Create new team", - "switch_to_personal": "Switch to your personal workspace", - "title": "Teams" + "delete": "حذف الفريق الحالي", + "edit": "تحرير الفريق الحالي", + "invite": "دعوة أشخاص إلى الفريق", + "new": "إنشاء فريق جديد", + "switch_to_personal": "الانتقال إلى مساحة العمل الشخصية", + "title": "الفرق" + }, + "phrases": { + "try": "جرب", + "import_collections": "استيراد المجموعات", + "create_environment": "إنشاء بيئة", + "create_workspace": "إنشاء مساحة عمل", + "share_request": "مشاركة الطلب" } }, "sse": { "event_type": "نوع الحدث", - "log": "سجل", + "log": "السجل", "url": "URL" }, "state": { @@ -826,112 +890,114 @@ "connected": "متصل", "connected_to": "متصل بـ {name}", "connecting_to": "جارٍ الاتصال بـ {name} ...", - "connection_error": "Failed to connect", - "connection_failed": "Connection failed", - "connection_lost": "Connection lost", - "copied_interface_to_clipboard": "Copied {language} interface type to clipboard", + "connection_error": "فشل الاتصال", + "connection_failed": "فشل الاتصال", + "connection_lost": "انقطع الاتصال", + "copied_interface_to_clipboard": "تم نسخ نوع واجهة {language} إلى الحافظة", "copied_to_clipboard": "نسخ إلى الحافظة", "deleted": "تم الحذف", - "deprecated": "إهمال", - "disabled": "معاق", + "deprecated": "مهمل", + "disabled": "معطل", "disconnected": "انقطع الاتصال", "disconnected_from": "انقطع الاتصال بـ {name}", "docs_generated": "تم إنشاء الوثائق", - "download_failed": "Download failed", + "download_failed": "فشل التنزيل", "download_started": "بدأ التنزيل", - "enabled": "ممكن", + "enabled": "ممكّن", "file_imported": "تم استيراد الملف", "finished_in": "انتهى في {duration} مللي ثانية", - "hide": "Hide", + "hide": "إخفاء", "history_deleted": "تم حذف السجل", - "linewrap": "خطوط الالتفاف", + "linewrap": "التفاف السطور", "loading": "تحميل...", - "message_received": "Message: {message} arrived on topic: {topic}", - "mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}", - "none": "لا شيئ", - "nothing_found": "لم يتم العثور على أي شيئ", - "published_error": "Something went wrong while publishing msg: {topic} to topic: {message}", - "published_message": "Published message: {message} to topic: {topic}", - "reconnection_error": "Failed to reconnect", - "show": "Show", - "subscribed_failed": "Failed to subscribe to topic: {topic}", - "subscribed_success": "Successfully subscribed to topic: {topic}", - "unsubscribed_failed": "Failed to unsubscribe from topic: {topic}", - "unsubscribed_success": "Successfully unsubscribed from topic: {topic}", + "message_received": "الرسالة: {message} وصلت على الموضوع: {topic}", + "mqtt_subscription_failed": "حدث خطأ ما أثناء الاشتراك في الموضوع: {topic}", + "none": "لا شيء", + "nothing_found": "لم يتم العثور على شيء", + "published_error": "حدث خطأ ما أثناء نشر الرسالة: {message} على الموضوع: {topic}", + "published_message": "تم نشر الرسالة: {message} على الموضوع: {topic}", + "reconnection_error": "فشل إعادة الاتصال", + "show": "إظهار", + "subscribed_failed": "فشل الاشتراك في الموضوع: {topic}", + "subscribed_success": "تم الاشتراك بنجاح في الموضوع: {topic}", + "unsubscribed_failed": "فشل إلغاء الاشتراك من الموضوع: {topic}", + "unsubscribed_success": "تم إلغاء الاشتراك بنجاح من الموضوع: {topic}", "waiting_send_request": "في انتظار إرسال الطلب" }, "support": { "changelog": "اقرأ المزيد عن أحدث الإصدارات", "chat": "أسئلة؟ دردش معنا!", "community": "اطرح الأسئلة وساعد الآخرين", - "documentation": "اقرأ المزيد عن Hoppscotch", - "forum": "اسأل سؤالا وتلقى جوابا", - "github": "Follow us on Github", + "documentation": "اقرأ المزيد عن هوبسكوتش", + "forum": "اسأل سؤالاً وتلقَّ جواباً", + "github": "تابعنا على جيت هاب", "shortcuts": "تصفح التطبيق بشكل أسرع", - "team": "تواصل مع الفريق", - "title": "يدعم", - "twitter": "تابعنا على تويتر" + "title": "الدعم", + "twitter": "تابعنا على تويتر", + "team": "تواصل مع الفريق" }, "tab": { - "authorization": "تفويض", - "body": "الجسم", - "close": "Close Tab", - "close_others": "Close other Tabs", + "authorization": "Authorization", + "body": "Body", + "close": "إغلاق", + "close_others": "إغلاق كل التبويبات الأخرى", "collections": "المجموعات", - "documentation": "توثيق", - "duplicate": "Duplicate Tab", - "environments": "Environments", - "headers": "الرؤوس", - "history": "تاريخ", + "documentation": "المستندات", + "duplicate": "تكرار التبويب", + "environments": "البيئات", + "headers": "Headers", + "history": "السجل", "mqtt": "MQTT", - "parameters": "حدود", + "parameters": "Parameters", "pre_request_script": "البرنامج النصي للطلب المسبق", - "queries": "استفسارات", - "query": "استفسار", - "schema": "مخطط", - "shared_requests": "Shared Requests", + "queries": "Queries", + "query": "Query", + "schema": "Schema", + "shared_requests": "الطلبات المشتركة", + "codegen": "توليد الكود", + "code_snippet": "مقتطف الكود", + "share_tab_request": "مشاركة طلب التبويب", "socketio": "مقبس", "sse": "SSE", "tests": "الاختبارات", "types": "أنواع", "variables": "المتغيرات", - "websocket": "مقبس الويب" + "websocket": "WebSocket" }, "team": { - "already_member": "انت بالفعل عضو في هذا الفريق! اتصل بمدير الفريق.", + "already_member": "أنت بالفعل عضو في هذا الفريق! اتصل بمدير الفريق.", "create_new": "أنشئ فريقًا جديدًا", "deleted": "تم حذف الفريق", "edit": "تحرير الفريق", "email": "بريد إلكتروني", - "email_do_not_match": "البريد الإلكتروني لا يتوافق مع معلومات حسابك. اتصل بمدير الفريق.", - "exit": "فريق الخروج", - "exit_disabled": "فقط المالك لا يمكنه الخروج من الفريق", - "failed_invites": "Failed invites", - "invalid_coll_id": "Invalid collection ID", + "email_do_not_match": "البريد الإلكتروني لا يتطابق مع معلومات حسابك. اتصل بمدير الفريق.", + "exit": "الخروج من الفريق", + "exit_disabled": "فقط المالك يمكنه الخروج من الفريق", + "failed_invites": "الدعوات الفاشلة", + "invalid_coll_id": "رقم المجموعة غير صالح", "invalid_email_format": "تنسيق البريد الإلكتروني غير صالح", - "invalid_id": "معرف الفريق غير صالح. اتصل بمدير الفريق.", + "invalid_id": "رقم الفريق غير صالح. اتصل بمدير الفريق.", "invalid_invite_link": "رابط الدعوة غير صالح", - "invalid_invite_link_description": "الرابط غير صالح. اتصل بمدير الفريق.", + "invalid_invite_link_description": "الرابط غير صالح أو منتهي. اتصل بمدير الفريق.", "invalid_member_permission": "يرجى تقديم إذن صالح لعضو الفريق", "invite": "دعوة", - "invite_more": "ادعو المزيد", - "invite_tooltip": "ادعو الناس الى العمل", - "invited_to_team": "{owner} قادم بدعوتك للإنضمام الى {team}", + "invite_more": "دعوة المزيد", + "invite_tooltip": "دعوة الأشخاص للانضمام", + "invited_to_team": "{owner} دعاك للانضمام إلى فريق {team}", "join": "تم قبول الدعوة", - "join_beta": "انضم إلى برنامج بيتا للوصول إلى الفرق.", - "join_team": "انصم الى فريق {team}", - "joined_team": "لقد انضممت الى فريق {team}", - "joined_team_description": "انت الآن عضو في الفريق", + "join_team": "انضم إلى فريق {team}", + "joined_team": "لقد انضممت إلى فريق {team}", + "joined_team_description": "أنت الآن عضو في الفريق", "left": "لقد تركت الفريق", "login_to_continue": "سجل الدخول للإكمال", - "login_to_continue_description": "عليك تسجيل الدخول لكي تنضم الى فريق", - "logout_and_try_again": "سجل الخروج و ادخل بحساب آخر", - "member_has_invite": "معرف البريد الإلكتروني لديه دعوة بالفعل. اتصل بمدير الفريق.", + "login_to_continue_description": "يجب عليك تسجيل الدخول لكي تنضم إلى الفريق", + "logout_and_try_again": "سجل الخروج وادخل بحساب آخر", + "member_has_invite": "البريد الإلكتروني لديه دعوة بالفعل. اتصل بمدير الفريق.", "member_not_found": "لم يتم العثور على العضو. اتصل بمدير الفريق.", "member_removed": "تمت إزالة المستخدم", "member_role_updated": "تم تحديث أدوار المستخدم", - "members": "أعضاء", - "more_members": "+{count} more", + "members": "الأعضاء", + "more_members": "+{count} أعضاء آخرين", "name_length_insufficient": "يجب أن يتكون اسم الفريق من 6 أحرف على الأقل", "name_updated": "تم تحديث اسم الفريق", "new": "فريق جديد", @@ -939,33 +1005,38 @@ "new_name": "فريقي الجديد", "no_access": "ليس لديك حق التعديل في هذه المجموعات", "no_invite_found": "لم يتم العثور على الدعوة. اتصل بمدير الفريق", - "no_request_found": "Request not found.", - "not_found": "Team not found. Contact your team owner.", - "not_valid_viewer": "You are not a valid viewer. Contact your team owner.", - "parent_coll_move": "Cannot move collection to a child collection", + "no_request_found": "لم يتم العثور على الطلب.", + "not_found": "الفريق غير موجود. اتصل بمالك الفريق.", + "not_valid_viewer": "أنت لست مشاهدًا صالحًا. اتصل بمالك الفريق.", + "parent_coll_move": "لا يمكن نقل المجموعة إلى مجموعة فرعية", "pending_invites": "دعوات معلقة", - "permissions": "أذونات", - "same_target_destination": "Same target and destination", - "saved": "فريق حفظ", - "select_a_team": "اختر فريق", - "success_invites": "Success invites", - "title": "فرق", - "we_sent_invite_link": "لقد أرسلنا رابط دعوة لجميع المدعوين!", - "we_sent_invite_link_description": "اطلب من جميع المدعوين التحقق من صندوق الوارد الخاص بهم. انقر على الرابط للانضمام إلى الفريق." + "permissions": "الأذونات", + "same_target_destination": "نفس الهدف والوجهة", + "saved": "تم حفظ الفريق", + "select_a_team": "اختر فريقًا", + "success_invites": "الدعوات الناجحة", + "title": "الفرق", + "we_sent_invite_link": "لقد أرسلنا رابط الدعوة لجميع المدعوين!", + "invite_sent_smtp_disabled": "تم إنشاء روابط الدعوة", + "we_sent_invite_link_description": "اطلب من جميع المدعوين التحقق من صندوق الوارد الخاص بهم. انقر على الرابط للانضمام إلى الفريق.", + "invite_sent_smtp_disabled_description": "إرسال رسائل الدعوة معطل لهذه النسخة من هوبسكوتش. يرجى استخدام زر نسخ الرابط لنسخ الرابط ومشاركته يدويًا.", + "copy_invite_link": "نسخ رابط الدعوة", + "search_title": "طلبات الفريق", + "join_beta": "انضم إلى برنامج بيتا للوصول إلى الفرق." }, "team_environment": { - "deleted": "Environment Deleted", - "duplicate": "Environment Duplicated", - "not_found": "Environment not found." + "deleted": "تم حذف البيئة", + "duplicate": "تم تكرار البيئة", + "not_found": "البيئة غير موجودة" }, "test": { - "failed": "فشل التجربة", + "failed": "فشل test", "javascript_code": "كود جافا سكريبت", "learn": "اقرأ الوثائق", - "passed": "نجاح التجربة", - "report": "تقرير الاختبار", - "results": "نتائج الإختبار", - "script": "النصي", + "passed": "نجاح test", + "report": "تقرير test", + "results": "نتائج test", + "script": "السكربت", "snippets": "المقتطفات" }, "websocket": { @@ -976,18 +1047,59 @@ "url": "URL" }, "workspace": { - "change": "Change workspace", - "personal": "My Workspace", - "team": "Team Workspace", - "title": "Workspaces" + "change": "تغيير مساحة العمل", + "personal": "مساحتي الشخصية", + "other_workspaces": "مساحاتي", + "team": "مساحة عمل الفريق", + "title": "مساحات العمل" + }, + "site_protection": { + "login_to_continue": "تسجيل الدخول للمتابعة", + "login_to_continue_description": "تحتاج إلى تسجيل الدخول للوصول إلى هذه النسخة من هوبسكوتش للمؤسسات.", + "error_fetching_site_protection_status": "حدث خطأ أثناء جلب حالة حماية الموقع" + }, + "access_tokens": { + "tab_title": "الرموز", + "section_title": "الرموز الشخصية", + "section_description": "تساعدك الرموز الشخصية على ربط CLI بحسابك في هوبسكوتش", + "last_used_on": "آخر استخدام في", + "expires_on": "تنتهي في", + "no_expiration": "بدون انتهاء", + "expired": "منتهية الصلاحية", + "copy_token_warning": "تأكد من نسخ الرمز الشخصي الآن. لن تتمكن من رؤيته مرة أخرى!", + "token_purpose": "ما الغرض من هذا الرمز؟", + "expiration_label": "تاريخ الانتهاء", + "scope_label": "نطاق", + "workspace_read_only_access": "وصول للقراءة فقط إلى بيانات مساحة العمل.", + "personal_workspace_access_limitation": "لا يمكن للرموز الشخصية الوصول إلى مساحة العمل الشخصية.", + "generate_token": "توليد رمز", + "invalid_label": "يرجى تقديم تسمية صالحة للرمز", + "no_expiration_verbose": "هذا الرمز لن ينتهي أبدًا!", + "token_expires_on": "سينتهي هذا الرمز في", + "generate_new_token": "توليد رمز جديد", + "generate_modal_title": "رمز وصول شخصي جديد", + "deletion_success": "تم حذف رمز الوصول {label}" + }, + "collection_runner": { + "collection_id": "رقم المجموعة", + "environment_id": "رقم البيئة", + "cli_collection_id_description": "سيتم استخدام رقم المجموعة هذا بواسطة مشغل المجموعة عبر CLI لـ هوبسكوتش.", + "cli_environment_id_description": "سيتم استخدام رقم البيئة هذا بواسطة مشغل المجموعة عبر CLI لـ هوبسكوتش.", + "include_active_environment": "تضمين البيئة النشطة:", + "cli": "CLI", + "ui": "المشغل (قريبًا)", + "cli_command_generation_description_cloud": "انسخ الأمر أدناه وقم بتشغيله من CLI. يرجى تحديد رمز الوصول الشخصي.", + "cli_command_generation_description_sh": "انسخ الأمر أدناه وقم بتشغيله من CLI. يرجى تحديد رمز الوصول الشخصي والتحقق من عنوان URL لخادم مثيل SH المُولّد.", + "cli_command_generation_description_sh_with_server_url_placeholder": "انسخ الأمر أدناه وقم بتشغيله من CLI. يرجى تحديد رمز الوصول الشخصي وعنوان URL لخادم مثيل SH.", + "run_collection": "تشغيل المجموعة" }, "shortcodes": { - "actions": "Actions", - "created_on": "Created on", - "deleted": "Shortcode deleted", - "method": "Method", - "not_found": "Shortcode not found", - "short_code": "Short code", - "url": "URL" + "actions": "الإجراءات", + "created_on": "تم الإنشاء في", + "deleted": "تم حذف الرمز القصير", + "method": "الطريقة", + "not_found": "الرمز القصير غير موجود", + "short_code": "رمز قصير", + "url": "الرابط" } } diff --git a/packages/hoppscotch-common/locales/ca.json b/packages/hoppscotch-common/locales/ca.json index 91e2cd8d9e..1dab5973a1 100644 --- a/packages/hoppscotch-common/locales/ca.json +++ b/packages/hoppscotch-common/locales/ca.json @@ -24,8 +24,10 @@ "go_back": "Tornar", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Etiquetar", "learn_more": "Aprèn més", + "download_here": "Download here", "less": "Menys", "more": "Més", "new": "Novetat", @@ -43,6 +45,7 @@ "search": "Cercar", "send": "Enviar", "share": "Share", + "show_secret": "Show secret", "start": "Començar", "starting": "Starting", "stop": "Aturar", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Escriviu una comanda o cerqueu...", "we_use_cookies": "Utilitzem cookies", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Què hi ha de nou?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "El compte existeix amb credencials diferents - Inicieu sessió per enllaçar els dos comptes", "all_sign_in_options": "Totes les opcions d'inici de sessió", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Continuar amb el correu electrònic", "continue_with_github": "Continuar amb GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Continuar amb Google", "continue_with_microsoft": "Continuar amb Microsoft", "continue_with_oidc": "Continuar amb OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Passar per", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Contrasenya", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Token", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Editar la col·lecció", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Proporcioneu un nom vàlid per a la col·lecció", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Seleccionar una col·lecció", "select_location": "Seleccionar la ubicació", + "details": "Details", "select_team": "Seleccionar un equip", "team_collections": "Col·leccions per equips" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Està segur que vol desactivar Telemetry?", "request_change": "Està segur que vol descartar la sol·licitud actual, els canvis no desats es perdran.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Està segur que vol sincronitzar aquest espai de treball?" + "sync": "Està segur que vol sincronitzar aquest espai de treball?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "No hi ha invitacions pendents per a aquest equip", "profile": "Inicia sessió per veure el vostre perfil", "protocols": "Els protocols estan buits", + "request_variables": "This request does not have any request variables", "schema": "Connecta't a un endpoint GraphQL", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "El nom de l'equip és buit", "teams": "Els equips estan buits", "tests": "No hi ha proves per a aquesta sol·licitud", + "access_tokens": "Access tokens are empty", "shortcodes": "Els shortcodes estan buits" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Seleccioneu un entorn", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Entorn actualitzat", "value": "Value", "variable": "Variable", - "variable_list": "Llista de variables" + "variables": "Variables", + "variable_list": "Llista de variables", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Nom de la sol·licitud buida", "f12_details": "(F12 per obtenir més informació)", "gql_prettify_invalid_query": "No s'ha pogut definir una consulta no vàlida, resoldre els errors de sintaxi de la consulta i tornar-ho a provar", @@ -297,6 +337,7 @@ "incorrect_email": "Correu electrònic incorrecte", "invalid_link": "Enllaç invalid", "invalid_link_description": "L'enllaç en que heu fet clic no és vàlid o ha caducat.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "JSON no vàlid", "json_prettify_invalid_body": "No s'ha pogut personalitzar un cos no vàlid, resol els errors de sintaxi json i tornar-ho a provar", "network_error": "Sembla que hi ha un error de xarxa. Si us plau torna-ho a provar.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "No s'ha pogut executar l'script de sol·licitud prèvia", "something_went_wrong": "Alguna cosa ha anat malament", - "test_script_fail": "No s'ha pogut executar l'script posterior a la sol·licitud" + "test_script_fail": "No s'ha pogut executar l'script posterior a la sol·licitud", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Exporta com a JSON", "create_secret_gist": "Crear un Gist secret", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist creat", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Inicieu la sessió amb GitHub per crear un Gisst secret", - "title": "Exportar" + "title": "Exportar", + "success": "Successfully exported", + "gist_created": "Gist creat" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutacions", "schema": "Esquema", "subscriptions": "Subscripcions", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Importar col·leccions des d'un fitxer JSON de col·leccions Hoppscotch", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Importació" + "title": "Importació", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Cos de sol·licitud sense processar", "rename": "Rename Request", "renamed": "S'ha canviat el nom de la sol·licitud", + "request_variables": "Request variables", "run": "Executar", "save": "Guardar", "save_as": "Guardar com", @@ -558,6 +614,7 @@ "title": "Sol·licitud", "type": "Tipus de sol·licitud", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Variables", "view_my_links": "Visualitzar els meus enllaços", "copy_link": "Copia l'enllaç" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Feu preguntes i obteniu respostes", "github": "Segueix-nos a Github", "shortcuts": "Navega per l'aplicació més ràpidament", - "team": "Poseu-vos en contacte amb l'equip", "title": "Suport", - "twitter": "Segueix-nos a Twitter" + "twitter": "Segueix-nos a Twitter", + "team": "Poseu-vos en contacte amb l'equip" }, "tab": { "authorization": "Autorització", @@ -890,6 +954,9 @@ "query": "Consulta", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Proves", @@ -918,7 +985,6 @@ "invite_tooltip": "Invitar persones a aquest espai de treball", "invited_to_team": "{owner} t'ha invitat a unir-te a {team}", "join": "S'ha acceptat la invitació", - "join_beta": "Uneix-te al programa beta per accedir als equips.", "join_team": "Uneix-te a {team}", "joined_team": "T'has unit a {team}", "joined_team_description": "Ara ets membre d'aquest equip", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Equips", "we_sent_invite_link": "Hem enviat un enllaç d'invitació a tots els convidats!", - "we_sent_invite_link_description": "Demaneu a tots els convidats que comprovin la seva safata d'entrada. Feu clic a l'enllaç per unir-vos a l'equip." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Demaneu a tots els convidats que comprovin la seva safata d'entrada. Feu clic a l'enllaç per unir-vos a l'equip.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Uneix-te al programa beta per accedir als equips." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Accions", "created_on": "Creat el", diff --git a/packages/hoppscotch-common/locales/cn.json b/packages/hoppscotch-common/locales/cn.json index 7ae0942395..dd601b3b85 100644 --- a/packages/hoppscotch-common/locales/cn.json +++ b/packages/hoppscotch-common/locales/cn.json @@ -1,6 +1,6 @@ { "action": { - "add": "Add", + "add": "新增", "autoscroll": "自动滚动", "cancel": "取消", "choose_file": "选择文件", @@ -11,7 +11,7 @@ "connect": "连接", "connecting": "连接中", "copy": "复制", - "create": "Create", + "create": "新增", "delete": "删除", "disconnect": "断开连接", "dismiss": "忽略", @@ -24,8 +24,10 @@ "go_back": "返回", "go_forward": "前进", "group_by": "分组方式", + "hide_secret": "隐藏密钥", "label": "标签", "learn_more": "了解更多", + "download_here": "下载到此处", "less": "更少", "more": "更多", "new": "新增", @@ -33,7 +35,7 @@ "open_workspace": "打开工作区", "paste": "粘贴", "prettify": "美化", - "properties": "Properties", + "properties": "属性", "remove": "移除", "rename": "重命名", "restore": "恢复", @@ -42,7 +44,8 @@ "scroll_to_top": "滚动至顶部", "search": "搜索", "send": "发送", - "share": "Share", + "share": "分享", + "show_secret": "显示密钥", "start": "开始", "starting": "正在开始", "stop": "停止", @@ -63,7 +66,7 @@ "contact_us": "联系我们", "cookies": "Cookies", "copy": "复制", - "copy_interface_type": "Copy interface type", + "copy_interface_type": "复制接口类型", "copy_user_id": "复制认证 Token", "developer_option": "开发者选项", "developer_option_description": "开发者工具,有助于开发和维护 Hoppscotch。", @@ -79,7 +82,7 @@ "keyboard_shortcuts": "键盘快捷键", "name": "Hoppscotch", "new_version_found": "已发现新版本。刷新页面以更新。", - "open_in_hoppscotch": "Open in Hoppscotch", + "open_in_hoppscotch": "在 Hoppscotch 中打开", "options": "选项", "proxy_privacy_policy": "代理隐私政策", "reload": "重新加载", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "输入命令或搜索内容……", "we_use_cookies": "我们使用 cookies", + "updated_text": "Hoppscotch 已更新至 v{version} 🎉", "whats_new": "新增内容", + "see_whats_new": "查看更新内容", "wiki": "帮助" }, "auth": { "account_exists": "当前帐号已存在 - 登录以链接两个帐号", "all_sign_in_options": "所有登录选项", + "continue_with_auth_provider": "使用 {provider} 登录", "continue_with_email": "使用电子邮箱登录", "continue_with_github": "使用 GitHub 登录", + "continue_with_github_enterprise": "使用 GitHub 企业版登录", "continue_with_google": "使用 Google 登录", "continue_with_microsoft": "使用 Microsoft 登录", "continue_with_oidc": "使用 OIDC 登录", @@ -120,27 +127,48 @@ }, "authorization": { "generate_token": "生成令牌", - "graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", + "graphql_headers": "将 Authorization 请求头作为 payload 发送到 connection_init", "include_in_url": "包含在 URL 内", - "inherited_from": "Inherited {auth} from parent collection {collection} ", + "inherited_from": "从父级集合 {collection} 继承 {auth}", "learn": "了解更多", "oauth": { - "redirect_auth_server_returned_error": "Auth Server returned an error state", - "redirect_auth_token_request_failed": "Request to get the auth token failed", - "redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token", - "redirect_invalid_state": "Invalid State value present in the redirect", - "redirect_no_auth_code": "No Authorization Code present in the redirect", - "redirect_no_client_id": "No Client ID defined", - "redirect_no_client_secret": "No Client Secret Defined", - "redirect_no_code_verifier": "No Code Verifier Defined", - "redirect_no_token_endpoint": "No Token Endpoint Defined", - "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", - "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "redirect_auth_server_returned_error": "鉴权服务器返回了一个错误状态", + "redirect_auth_token_request_failed": "获取鉴权令牌失败", + "redirect_auth_token_request_invalid_response": "请求鉴权令牌时,令牌端点返回了无效响应", + "redirect_invalid_state": "重定向中存在无效的状态值", + "redirect_no_auth_code": "重定向中不存在授权码", + "redirect_no_client_id": "未定义客户端ID", + "redirect_no_client_secret": "未定义客户端密钥", + "redirect_no_code_verifier": "未定义代码验证器", + "redirect_no_token_endpoint": "未定义令牌端点", + "something_went_wrong_on_oauth_redirect": "OAuth 重定向过程中出现问题", + "something_went_wrong_on_token_generation": "令牌生成过程中出现问题", + "token_generation_oidc_discovery_failed": "令牌生成失败: OpenID Connect 发现失败", + "grant_type": "授权类型", + "grant_type_auth_code": "授权码", + "token_fetched_successfully": "令牌获取成功", + "token_fetch_failed": "令牌获取失败", + "validation_failed": "验证失败,请检查表单字段", + "label_authorization_endpoint": "授权端点", + "label_client_id": "客户端 ID", + "label_client_secret": "客户端密钥", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge 方法", + "label_code_verifier": "Code Verifier", + "label_scopes": "范围", + "label_token_endpoint": "令牌端点", + "label_use_pkce": "使用 PKCE", + "label_implicit": "隐式", + "label_password": "密码", + "label_username": "用户名", + "label_auth_code": "授权码", + "label_client_credentials": "客户端凭证" }, "pass_key_by": "传递方式", + "pass_by_query_params_label": "请求参数", + "pass_by_headers_label": "Headers", "password": "密码", - "save_to_inherit": "Please save this request in any collection to inherit the authorization", + "save_to_inherit": "为了继承鉴权信息,请保存这个请求到任意集合中", "token": "令牌", "type": "授权类型", "username": "用户名" @@ -149,7 +177,8 @@ "created": "集合已创建", "different_parent": "不能用不同的父类来重新排序集合", "edit": "编辑集合", - "import_or_create": "Import or create a collection", + "import_or_create": "导入或创建一个集合", + "import_collection": "导入集合", "invalid_name": "请提供有效的集合名称", "invalid_root_move": "该集合已经在根级了", "moved": "移动完成", @@ -158,14 +187,15 @@ "name_length_insufficient": "集合名字至少需要 3 个字符", "new": "新建集合", "order_changed": "集合顺序已更新", - "properties": "Collection Properties", - "properties_updated": "Collection Properties Updated", + "properties": "集合属性", + "properties_updated": "集合属性已更新", "renamed": "集合已更名", "request_in_use": "请求正在使用中", "save_as": "另存为", "save_to_collection": "保存至集合", "select": "选择一个集合", "select_location": "选择位置", + "details": "Details", "select_team": "选择一个团队", "team_collections": "团队集合" }, @@ -179,12 +209,13 @@ "remove_folder": "你确定要永久删除该文件夹吗?", "remove_history": "你确定要永久删除全部历史记录吗?", "remove_request": "你确定要永久删除该请求吗?", - "remove_shared_request": "Are you sure you want to permanently delete this shared request?", + "remove_shared_request": "你确定要永久删除该共享请求吗?", "remove_team": "你确定要删除该团队吗?", "remove_telemetry": "你确定要退出遥测服务吗?", "request_change": "你确定你要放弃当前的请求,未保存的修改将被丢失。", "save_unsaved_tab": "你想保存在此标签页中所作的修改吗?", - "sync": "您确定要同步该工作区吗?" + "sync": "您确定要同步该工作区吗?", + "delete_access_token": "你确定要删除这个授权令牌 {tokenLabel} 吗?" }, "context_menu": { "add_parameters": "添加至参数", @@ -193,20 +224,20 @@ }, "cookies": { "modal": { - "cookie_expires": "Expires", - "cookie_name": "Name", - "cookie_path": "Path", - "cookie_string": "Cookie string", - "cookie_value": "Value", - "empty_domain": "Domain is empty", - "empty_domains": "Domain list is empty", - "enter_cookie_string": "Enter cookie string", - "interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.", - "managed_tab": "Managed", - "new_domain_name": "New domain name", - "no_cookies_in_domain": "No cookies set for this domain", - "raw_tab": "Raw", - "set": "Set a cookie" + "cookie_expires": "过期时间", + "cookie_name": "名称", + "cookie_path": "路径", + "cookie_string": "Cookie 字符串", + "cookie_value": "值", + "empty_domain": "域名为空", + "empty_domains": "域名列表为空", + "enter_cookie_string": "输入 Cookie 字符串", + "interceptor_no_support": "你当前选择的中间件不支持 Cookie ,请选择一个其他的中间件并重试。", + "managed_tab": "管理", + "new_domain_name": "新域名", + "no_cookies_in_domain": "该域名没有 Cookie", + "raw_tab": "原始内容", + "set": "设置一个 Cookie" } }, "count": { @@ -238,13 +269,16 @@ "pending_invites": "此团队无待办邀请", "profile": "登录以查看你的个人资料", "protocols": "协议为空", + "request_variables": "这个请求没有任何请求变量", "schema": "连接至 GraphQL 端点", - "shared_requests": "Shared requests are empty", - "shared_requests_logout": "Login to view your shared requests or create a new one", + "secret_environments": "密钥不会被 Hoppscotch 同步", + "shared_requests": "共享请求为空", + "shared_requests_logout": "登录并查看你的共享请求或创建一个新的共享请求", "subscription": "订阅为空", "team_name": "团队名称为空", "teams": "团队为空", "tests": "没有针对该请求的测试", + "access_tokens": "鉴权令牌为空", "shortcodes": "短链接为空" }, "environment": { @@ -258,7 +292,7 @@ "empty_variables": "没有变量", "global": "全局", "global_variables": "全局变量", - "import_or_create": "Import or create a environment", + "import_or_create": "导入或创建一个环境", "invalid_name": "请提供有效的环境名称", "list": "环境变量", "my_environments": "我的环境", @@ -271,6 +305,8 @@ "quick_peek": "快速浏览环境", "replace_with_variable": "替换为变量", "scope": "范围", + "secrets": "密钥", + "secret_value": "密钥值", "select": "选择环境", "set": "设置环境", "set_as_environment": "设置为环境", @@ -279,17 +315,21 @@ "updated": "环境已更新", "value": "值", "variable": "变量", - "variable_list": "变量列表" + "variables": "Variables", + "variable_list": "变量列表", + "properties": "环境属性", + "details": "详情" }, "error": { - "authproviders_load_error": "Unable to load auth providers", + "authproviders_load_error": "无法加载鉴权提供者", "browser_support_sse": "该浏览器似乎不支持 SSE。", "check_console_details": "检查控制台日志以获悉详情", - "check_how_to_add_origin": "Check how you can add an origin", + "check_how_to_add_origin": "检查如何添加源", "curl_invalid_format": "cURL 格式不正确", "danger_zone": "危险区域", "delete_account": "您的帐号目前为这些团队的拥有者:", "delete_account_description": "您在删除帐号前必须先将您自己从团队中移除、转移拥有权,或是删除团队。", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "空请求名称", "f12_details": "(F12 详情)", "gql_prettify_invalid_query": "无法美化无效的查询,处理查询语法错误并重试", @@ -297,37 +337,42 @@ "incorrect_email": "电子邮箱错误", "invalid_link": "无效链接", "invalid_link_description": "你点击的链接无效或已过期。", - "invalid_embed_link": "The embed does not exist or is invalid.", + "invalid_embed_link": "内嵌内容不存在或者失效。", "json_parsing_failed": "不合法的 JSON", "json_prettify_invalid_body": "无法美化无效的请求头,处理 JSON 语法错误并重试", "network_error": "好像发生了网络错误,请重试。", "network_fail": "无法发送请求", - "no_collections_to_export": "No collections to export. Please create a collection to get started.", + "no_collections_to_export": "导出集合为空,请先创建一个集合。", "no_duration": "无持续时间", - "no_environments_to_export": "No environments to export. Please create an environment to get started.", + "no_environments_to_export": "导出集合环境,请先创建一个环境。", "no_results_found": "找不到结果", "page_not_found": "找不到此頁面", - "please_install_extension": "Please install the extension and add origin to the extension.", + "please_install_extension": "请安装扩展并将源添加至扩展。", "proxy_error": "代理错误", + "same_profile_name": "更新后的配置文件名称与当前配置文件名称相同", "script_fail": "无法执行预请求脚本", "something_went_wrong": "发生了一些错误", - "test_script_fail": "无法执行请求脚本" + "test_script_fail": "无法执行请求脚本", + "reading_files": "读取一个或多个文件时出错。", + "fetching_access_tokens_list": "获取令牌列表时出错", + "generate_access_token": "生成访问令牌时出错", + "delete_access_token": "删除访问令牌时出错" }, "export": { "as_json": "导出为 JSON", "create_secret_gist": "创建私密 Gist", - "create_secret_gist_tooltip_text": "Export as secret Gist", - "failed": "Something went wrong while exporting", - "secret_gist_success": "Successfully exported as secret Gist", + "create_secret_gist_tooltip_text": "导出为私密 Gist", + "failed": "导出时发生了错误", + "secret_gist_success": "成功导出为私密 Gist", "require_github": "使用 GitHub 登录以创建私密 Gist", "title": "导出", - "success": "Successfully exported", + "success": "成功导出", "gist_created": "已创建 Gist" }, "filter": { "all": "全部", "none": "无", - "starred": "已加星号" + "starred": "已加星标" }, "folder": { "created": "已创建文件夹", @@ -344,10 +389,11 @@ "mutations": "变更", "schema": "模式", "subscriptions": "订阅", - "switch_connection": "切换连接" + "switch_connection": "切换连接", + "url_placeholder": "输入一个 GraphQL 端点 URL" }, "graphql_collections": { - "title": "GraphQL Collections" + "title": "GraphQL 集合" }, "group": { "time": "时间", @@ -360,8 +406,8 @@ }, "helpers": { "authorization": "授权头将会在你发送请求时自动生成。", - "collection_properties_authorization": " This authorization will be set for every request in this collection.", - "collection_properties_header": "This header will be set for every request in this collection.", + "collection_properties_authorization": "这个授权将被应用在当前集合下的所有请求。", + "collection_properties_header": "这个请求头将被应用在当前集合下的所有请求。", "generate_documentation_first": "请先生成文档", "network_fail": "无法到达 API 端点。请检查网络连接并重试。", "offline": "你似乎处于离线状态,该工作区中的数据可能不是最新。", @@ -381,10 +427,10 @@ "import": { "collections": "导入集合", "curl": "导入 cURL", - "environments_from_gist": "Import From Gist", - "environments_from_gist_description": "Import Hoppscotch Environments From Gist", + "environments_from_gist": "从 Gist 导入", + "environments_from_gist_description": "从 Gist 导入 Hoppscotch 环境", "failed": "导入失败", - "from_file": "Import from File", + "from_file": "从文件导入", "from_gist": "从 Gist 导入", "from_gist_description": "从 Gist URL 导入", "from_insomnia": "从 Insomnia 导入", @@ -399,23 +445,28 @@ "from_postman_description": "从 Postman 集合中导入", "from_url": "从 URL 导入", "gist_url": "输入 Gist URL", - "gql_collections_from_gist_description": "Import GraphQL Collections From Gist", - "hoppscotch_environment": "Hoppscotch Environment", - "hoppscotch_environment_description": "Import Hoppscotch Environment JSON file", + "gql_collections_from_gist_description": "从 Gist 导入 GraphQL 集合", + "hoppscotch_environment": "Hoppscotch 环境", + "hoppscotch_environment_description": "导入 Hoppscotch 环境 JSON 文件", "import_from_url_invalid_fetch": "无法从网址取得资料", "import_from_url_invalid_file_format": "导入组合时发生错误", "import_from_url_invalid_type": "不支持此类型。可接受的值为 'hoppscotch'、'openapi'、'postman'、'insomnia'", "import_from_url_success": "已导入组合", - "insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file", + "insomnia_environment_description": "从一个 JSON/YAML 文件中导入 Insomnia 环境", "json_description": "从 Hoppscotch 的集合文件导入(JSON)", - "postman_environment": "Postman Environment", - "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "导入" + "postman_environment": "Postman 环境", + "postman_environment_description": "从一个 JSON 文件中导入 Postman 环境", + "title": "导入", + "file_size_limit_exceeded_warning_multiple_files": "当前选择的文件大小超过了推荐的 10MB,只导入第一个被选择的 {files}。", + "file_size_limit_exceeded_warning_single_file": "当前选择的文件大小超过了推荐的 10MB,请选择其他文件", + "success": "成功导入" }, "inspections": { "description": "查可能的错误", "environment": { "add_environment": "添加到环境", + "add_environment_value": "添加值", + "empty_value": "环境变量“{variable}”的值为空", "not_found": "环境变量“{environment}”未找到。" }, "header": { @@ -447,10 +498,10 @@ "close_unsaved_tab": "有未保存的变更", "collections": "集合", "confirm": "确认", - "customize_request": "Customize Request", + "customize_request": "自定义请求", "edit_request": "编辑请求", "import_export": "导入/导出", - "share_request": "Share Request" + "share_request": "分享请求" }, "mqtt": { "already_subscribed": "您已经订阅了此主题。", @@ -466,12 +517,12 @@ "keep_alive": "Keep Alive", "log": "日志", "lw_message": "遗嘱消息", - "lw_qos": "遗嘱消息QoS", + "lw_qos": "遗嘱消息 QoS", "lw_retain": "遗嘱消息保留", "lw_topic": "遗嘱消息主题", "message": "消息", "new": "新订阅", - "not_connected": "请先启动MQTT连接。", + "not_connected": "请先启动 MQTT 连接。", "publish": "发布", "qos": "QoS", "ssl": "SSL", @@ -531,8 +582,8 @@ "enter_curl": "输入 cURL", "generate_code": "生成代码", "generated_code": "已生成代码", - "go_to_authorization_tab": "Go to Authorization tab", - "go_to_body_tab": "Go to Body tab", + "go_to_authorization_tab": "前往鉴权标签", + "go_to_body_tab": "前往请求体标签", "header_list": "请求头列表", "invalid_name": "请提供请求名称", "method": "方法", @@ -551,17 +602,19 @@ "raw_body": "原始请求体", "rename": "重命名请求", "renamed": "请求重命名", + "request_variables": "请求变量", "run": "运行", "save": "保存", "save_as": "另存为", "saved": "请求已保存", "share": "分享", "share_description": "分享 Hoppscotch 给你的朋友", - "share_request": "Share Request", + "share_request": "分享请求", "stop": "停止", "title": "请求", "type": "请求类型", "url": "URL", + "url_placeholder": "输入一个 URL 或者粘贴一个 cURL 命令", "variables": "变量", "view_my_links": "查看我的链接", "copy_link": "复制链接" @@ -581,7 +634,7 @@ "status": "状态", "time": "时间", "title": "响应", - "video": "Video", + "video": "视频", "waiting_for_connection": "等待连接", "xml": "XML" }, @@ -592,7 +645,7 @@ "account_description": "自定义您的帐户设置。", "account_email_description": "您的主要电子邮箱地址。", "account_name_description": "这是您的显示名称。", - "additional": "Additional Settings", + "additional": "其他设置", "background": "背景", "black_mode": "黑色", "choose_language": "选择语言", @@ -640,29 +693,29 @@ "verify_email": "验证电子邮箱" }, "shared_requests": { - "button": "Button", - "button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.", - "copy_html": "Copy HTML", - "copy_link": "Copy Link", - "copy_markdown": "Copy Markdown", - "creating_widget": "Creating widget", - "customize": "Customize", - "deleted": "Shared request deleted", - "description": "Select a widget, you can change and customize this later", - "embed": "Embed", - "embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.", - "link": "Link", - "link_info": "Create a shareable link to share with anyone on the internet with view access.", - "modified": "Shared request modified", - "not_found": "Shared request not found", - "open_new_tab": "Open in new tab", - "preview": "Preview", - "run_in_hoppscotch": "Run in Hoppscotch", + "button": "按钮", + "button_info": "为你的网站、博客或者 README 创建一个“Run in Hoppscotch”按钮。", + "copy_html": "复制 HTML", + "copy_link": "复制链接", + "copy_markdown": "复制 Markdown", + "creating_widget": "创建控件", + "customize": "自定义", + "deleted": "共享请求已删除", + "description": "选择一个控件,之后你可以更改或者自定义", + "embed": "内嵌", + "embed_info": "为你的网站、博客或者文档添加一个小的“Hoppscotch API Playground”。", + "link": "链接", + "link_info": "创建一个仅查看的分享链接给任何人", + "modified": "共享请求与修改", + "not_found": "未找到共享请求", + "open_new_tab": "在新标签中打开", + "preview": "预览", + "run_in_hoppscotch": "在 Hoppscotch 中运行", "theme": { - "dark": "Dark", - "light": "Light", - "system": "System", - "title": "Theme" + "dark": "深色", + "light": "浅色", + "system": "系统", + "title": "主题" } }, "shortcut": { @@ -707,7 +760,7 @@ "save_request": "保存请求", "save_to_collections": "保存到集合", "send_request": "发送请求", - "share_request": "Share Request", + "share_request": "共享请求", "show_code": "生成代码片段", "title": "请求", "copy_request_link": "复制请求链接" @@ -778,7 +831,7 @@ "tab_parameters": "参数标签页", "tab_pre_request_script": "预请求脚本标签页", "tab_query": "查询标签页", - "tab_tests": "测试标签页b", + "tab_tests": "测试标签页", "tab_variables": "变量标签页" }, "response": { @@ -816,6 +869,13 @@ "new": "创建新团队", "switch_to_personal": "切换到您的个人工作空间", "title": "团队" + }, + "phrases": { + "try": "Try", + "import_collections": "导入集合", + "create_environment": "创建环境", + "create_workspace": "创建工作区", + "share_request": "分享请求" } }, "sse": { @@ -833,7 +893,7 @@ "connection_error": "连接错误", "connection_failed": "连接失败", "connection_lost": "连接丢失", - "copied_interface_to_clipboard": "Copied {language} interface type to clipboard", + "copied_interface_to_clipboard": "复制 {language} 接口类型到剪贴板", "copied_to_clipboard": "已复制到剪贴板", "deleted": "已删除", "deprecated": "已弃用", @@ -841,7 +901,7 @@ "disconnected": "断开连接", "disconnected_from": "与 {name} 断开连接", "docs_generated": "已生成文档", - "download_failed": "Download failed", + "download_failed": "下载失败", "download_started": "开始下载", "enabled": "启用", "file_imported": "文件已导入", @@ -872,18 +932,18 @@ "forum": "答疑解惑", "github": "在 Github 关注我们", "shortcuts": "更快浏览应用", - "team": "与团队保持联系", "title": "支持", - "twitter": "在 Twitter 关注我们" + "twitter": "在 Twitter 关注我们", + "team": "与团队保持联系" }, "tab": { "authorization": "授权", "body": "请求体", - "close": "Close Tab", - "close_others": "Close other Tabs", + "close": "关闭标签页", + "close_others": "关闭所有其他标签页", "collections": "集合", "documentation": "帮助文档", - "duplicate": "Duplicate Tab", + "duplicate": "复制当前标签页", "environments": "环境", "headers": "请求头", "history": "历史记录", @@ -892,8 +952,11 @@ "pre_request_script": "预请求脚本", "queries": "查询", "query": "查询", - "schema": "Schema", - "shared_requests": "Shared Requests", + "schema": "模式", + "shared_requests": "共享请求", + "codegen": "生成代码", + "code_snippet": "代码片段", + "share_tab_request": "分享标签页请求", "socketio": "Socket.IO", "sse": "SSE", "tests": "测试", @@ -910,7 +973,7 @@ "email_do_not_match": "邮箱无法与你的帐户信息匹配。请联系你的团队者。", "exit": "退出团队", "exit_disabled": "团队所有者无法退出团队", - "failed_invites": "Failed invites", + "failed_invites": "邀请失败", "invalid_coll_id": "无效的集合 ID", "invalid_email_format": "电子邮箱格式无效", "invalid_id": "无效的团队 ID,请联系你的团队者。", @@ -922,7 +985,6 @@ "invite_tooltip": "邀请成员加入此工作区", "invited_to_team": "{owner} 邀请你加入 {team}", "join": "邀请已被接受", - "join_beta": "加入 Beta 计划以访问团队。", "join_team": "加入 {team}", "joined_team": "你已加入 {team}", "joined_team_description": "你现在是此团队的成员了", @@ -952,10 +1014,15 @@ "same_target_destination": "目标相同", "saved": "团队已保存", "select_a_team": "选择团队", - "success_invites": "Success invites", + "success_invites": "邀请成功", "title": "团队", "we_sent_invite_link": "我们向所有受邀者发送了邀请链接!", - "we_sent_invite_link_description": "请所有受邀者检查他们的收件箱,点击链接以加入团队。" + "invite_sent_smtp_disabled": "邀请链接已生成", + "we_sent_invite_link_description": "请所有受邀者检查他们的收件箱,点击链接以加入团队。", + "invite_sent_smtp_disabled_description": "发送邀请邮件在此 Hoppscotch 实例中已禁用。请使用“复制链接”按钮手动复制并分享邀请链接。", + "copy_invite_link": "复制邀请链接", + "search_title": "团队请求", + "join_beta": "加入 Beta 计划以访问团队。" }, "team_environment": { "deleted": "已刪除环境", @@ -982,9 +1049,50 @@ "workspace": { "change": "切换工作空间", "personal": "我的工作空间", + "other_workspaces": "My Workspaces", "team": "团队工作空间", "title": "工作空间" }, + "site_protection": { + "login_to_continue": "登录以继续", + "login_to_continue_description": "您需要登录才能访问此 Hoppscotch 企业实例。", + "error_fetching_site_protection_status": "获取站点保护状态时出现问题" + }, + "access_tokens": { + "tab_title": "令牌", + "section_title": "个人访问令牌", + "section_description": "个人访问令牌目前帮助您将 CLI 连接到您的 Hoppscotch 账户", + "last_used_on": "最后使用于", + "expires_on": "过期于", + "no_expiration": "未过期", + "expired": "已过期", + "copy_token_warning": "请确保现在复制您的个人访问令牌。您将无法再次看到它!", + "token_purpose": "此令牌的用途是什么?", + "expiration_label": "到期", + "scope_label": "范围", + "workspace_read_only_access": "对工作区数据的只读访问权限。", + "personal_workspace_access_limitation": "个人访问令牌无法访问您的个人工作区。", + "generate_token": "生成令牌", + "invalid_label": "请为令牌提供一个标签", + "no_expiration_verbose": "此令牌永不过期!", + "token_expires_on": "此令牌将在", + "generate_new_token": "生成新令牌", + "generate_modal_title": "新个人访问令牌", + "deletion_success": "访问令牌 {label} 已被删除" + }, + "collection_runner": { + "collection_id": "集合ID", + "environment_id": "环境ID", + "cli_collection_id_description": "此集合 ID 将由 Hoppscotch 的 CLI 集合运行器使用。", + "cli_environment_id_description": "此环境 ID 将由 Hoppscotch 的 CLI 集合运行器使用。", + "include_active_environment": "包含活动环境:", + "cli": "CLI", + "ui": "运行器(即将推出)", + "cli_command_generation_description_cloud": "复制以下命令并在 CLI 中运行。请指定个人访问令牌。", + "cli_command_generation_description_sh": "复制以下命令并在 CLI 中运行。请指定个人访问令牌并验证生成的SH 实例服务器 URL。", + "cli_command_generation_description_sh_with_server_url_placeholder": "复制以下命令并在 CLI 中运行。请指定个人访问令牌和 SH 实例服务器 URL。", + "run_collection": "运行集合" + }, "shortcodes": { "actions": "操作", "created_on": "创建于", diff --git a/packages/hoppscotch-common/locales/cs.json b/packages/hoppscotch-common/locales/cs.json index 2a7cdbef69..b966b6eab2 100644 --- a/packages/hoppscotch-common/locales/cs.json +++ b/packages/hoppscotch-common/locales/cs.json @@ -24,8 +24,10 @@ "go_back": "Vrať se", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Označení", "learn_more": "Další informace", + "download_here": "Download here", "less": "Less", "more": "Více", "new": "Nový", @@ -43,6 +45,7 @@ "search": "Vyhledávání", "send": "Poslat", "share": "Share", + "show_secret": "Show secret", "start": "Start", "starting": "Starting", "stop": "Stop", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Zadejte příkaz nebo hledejte…", "we_use_cookies": "Používáme cookies", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Co je nového?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Účet existuje s různými pověřeními - Přihlaste se a propojte oba účty", "all_sign_in_options": "Všechny možnosti přihlášení", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Pokračujte e -mailem", "continue_with_github": "Pokračujte na GitHubu", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Pokračovat s Google", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "Pokračujte s OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Heslo", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Žeton", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Upravit sbírku", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Uveďte prosím platný název kolekce", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Vyberte sbírku", "select_location": "Vyberte umístění", + "details": "Details", "select_team": "Vyberte tým", "team_collections": "Týmové sbírky" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Opravdu se chcete odhlásit z telemetrie?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Opravdu chcete synchronizovat tento pracovní prostor?" + "sync": "Opravdu chcete synchronizovat tento pracovní prostor?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "There are no pending invites for this team", "profile": "Login to view your profile", "protocols": "Protokoly jsou prázdné", + "request_variables": "This request does not have any request variables", "schema": "Připojte se ke koncovému bodu GraphQL", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Název týmu prázdný", "teams": "Týmy jsou prázdné", "tests": "Pro tento požadavek neexistují žádné testy", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Vyberte prostředí", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment updation", "value": "Value", "variable": "Variable", - "variable_list": "Seznam proměnných" + "variables": "Variables", + "variable_list": "Seznam proměnných", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Název prázdného požadavku", "f12_details": "(F12 pro podrobnosti)", "gql_prettify_invalid_query": "Neplatný dotaz nelze předběžně upravit, vyřešit chyby syntaxe dotazu a zkusit to znovu", @@ -297,6 +337,7 @@ "incorrect_email": "Incorrect email", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "Nelze předtifikovat neplatné tělo, vyřešit chyby syntaxe json a zkusit to znovu", "network_error": "There seems to be a network error. Please try again.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Skript předběžného požadavku nelze spustit", "something_went_wrong": "Něco se pokazilo", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Exportovat jako JSON", "create_secret_gist": "Vytvořte tajnou podstatu", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Podstata vytvořena", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Přihlaste se pomocí GitHub a vytvořte tajný seznam", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Podstata vytvořena" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutace", "schema": "Schéma", "subscriptions": "Předplatné", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Import" + "title": "Import", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Raw Request Body", "rename": "Rename Request", "renamed": "Žádost přejmenována", + "request_variables": "Request variables", "run": "Běh", "save": "Uložit", "save_as": "Uložit jako", @@ -558,6 +614,7 @@ "title": "Žádost", "type": "Typ požadavku", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Proměnné", "view_my_links": "View my links", "copy_link": "Kopírovat odkaz" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Ptejte se a získejte odpovědi", "github": "Follow us on Github", "shortcuts": "Procházejte aplikaci rychleji", - "team": "Spojte se s týmem", "title": "Podpěra, podpora", - "twitter": "Sleduj nás na Twitteru" + "twitter": "Sleduj nás na Twitteru", + "team": "Spojte se s týmem" }, "tab": { "authorization": "Povolení", @@ -890,6 +954,9 @@ "query": "Dotaz", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Testy", @@ -918,7 +985,6 @@ "invite_tooltip": "Invite people to this workspace", "invited_to_team": "{owner} invited you to join {team}", "join": "Invitation accepted", - "join_beta": "Připojte se k beta programu a získejte přístup k týmům.", "join_team": "Join {team}", "joined_team": "You have joined {team}", "joined_team_description": "You are now a member of this team", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Týmy", "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Připojte se k beta programu a získejte přístup k týmům." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/da.json b/packages/hoppscotch-common/locales/da.json index 1df5a88712..22ee8d40b6 100644 --- a/packages/hoppscotch-common/locales/da.json +++ b/packages/hoppscotch-common/locales/da.json @@ -1,56 +1,59 @@ { "action": { - "add": "Add", - "autoscroll": "Autoscroll", - "cancel": "Afbestille", + "add": "Tilføj", + "autoscroll": "Automatisk rulning", + "cancel": "Annuller", "choose_file": "Vælg en fil", - "clear": "Klar", - "clear_all": "Slet alt", - "clear_history": "Clear all History", - "close": "Close", - "connect": "Opret forbindelse", - "connecting": "Connecting", - "copy": "Kopi", - "create": "Create", + "clear": "Ryd", + "clear_all": "Ryd alt", + "clear_history": "Ryd al historik", + "close": "Luk", + "connect": "Forbind", + "connecting": "Forbinder", + "copy": "Kopiér", + "create": "Opret", "delete": "Slet", - "disconnect": "Koble fra", - "dismiss": "Afskedige", - "dont_save": "Don't save", + "disconnect": "Afbryd forbindelse", + "dismiss": "Afvis", + "dont_save": "Gem ikke", "download_file": "Download fil", - "drag_to_reorder": "Drag to reorder", - "duplicate": "Duplicate", - "edit": "Redigere", - "filter": "Filter", + "drag_to_reorder": "Træk for at omarrangere", + "duplicate": "Duplikér", + "edit": "Redigér", + "filter": "Filtrer", "go_back": "Gå tilbage", - "go_forward": "Go forward", - "group_by": "Group by", + "go_forward": "Gå fremad", + "group_by": "Gruppér efter", + "hide_secret": "Skjul hemmelighed", "label": "Etiket", "learn_more": "Lær mere", - "less": "Less", + "download_here": "Download her", + "less": "Mindre", "more": "Mere", "new": "Ny", - "no": "Ingen", - "open_workspace": "Open workspace", - "paste": "Paste", - "prettify": "Prettify", - "properties": "Properties", - "remove": "Fjerne", - "rename": "Rename", + "no": "Nej", + "open_workspace": "Åbn arbejdsområde", + "paste": "Indsæt", + "prettify": "Forskøn", + "properties": "Egenskaber", + "remove": "Fjern", + "rename": "Omdøb", "restore": "Gendan", - "save": "Gemme", - "scroll_to_bottom": "Scroll to bottom", - "scroll_to_top": "Scroll to top", + "save": "Gem", + "scroll_to_bottom": "Rul til bunden", + "scroll_to_top": "Rul til toppen", "search": "Søg", - "send": "Sende", - "share": "Share", + "send": "Send", + "share": "Del", + "show_secret": "Vis hemmelighed", "start": "Start", - "starting": "Starting", - "stop": "Hold op", - "to_close": "to close", - "to_navigate": "to navigate", - "to_select": "to select", + "starting": "Starter", + "stop": "Stop", + "to_close": "for at lukke", + "to_navigate": "for at navigere", + "to_select": "for at vælge", "turn_off": "Sluk", - "turn_on": "Tænde for", + "turn_on": "Tænd", "undo": "Fortryd", "yes": "Ja" }, @@ -62,155 +65,182 @@ "chat_with_us": "Chat med os", "contact_us": "Kontakt os", "cookies": "Cookies", - "copy": "Kopi", - "copy_interface_type": "Copy interface type", - "copy_user_id": "Copy User Auth Token", - "developer_option": "Developer options", - "developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.", + "copy": "Kopiér", + "copy_interface_type": "Kopiér interfacetype", + "copy_user_id": "Kopiér brugerautentifikationstoken", + "developer_option": "Udviklermuligheder", + "developer_option_description": "Udviklingsværktøjer, der hjælper med udvikling og vedligeholdelse af Hoppscotch.", "discord": "Discord", "documentation": "Dokumentation", "github": "GitHub", - "help": "Hjælp, feedback og dokumentation", + "help": "Hjælp og feedback", "home": "Hjem", - "invite": "Invitere", - "invite_description": "I Hoppscotch har vi designet en enkel og intuitiv grænseflade til oprettelse og administration af dine API'er. Hoppscotch er et værktøj, der hjælper dig med at opbygge, teste, dokumentere og dele dine API'er.", - "invite_your_friends": "Inviter dine venner", - "join_discord_community": "Deltag i vores Discord -fællesskab", + "invite": "Invitér", + "invite_description": "Hoppscotch er et open source API-udviklingsøkosystem. Vi har designet en simpel og intuitiv brugerflade til at oprette og administrere dine API'er. Hoppscotch er et værktøj, der hjælper dig med at bygge, teste, dokumentere og dele dine API'er.", + "invite_your_friends": "Invitér dine venner", + "join_discord_community": "Tilslut dig vores Discord-fællesskab", "keyboard_shortcuts": "Tastaturgenveje", "name": "Hoppscotch", - "new_version_found": "Ny version fundet. Opdater for at opdatere.", - "open_in_hoppscotch": "Open in Hoppscotch", - "options": "Options", - "proxy_privacy_policy": "Politik til beskyttelse af personlige oplysninger i proxy", + "new_version_found": "Ny version fundet. Opdatér for at opdatere.", + "open_in_hoppscotch": "Åbn i Hoppscotch", + "options": "Indstillinger", + "proxy_privacy_policy": "Proxy privatlivspolitik", "reload": "Genindlæs", "search": "Søg", "share": "Del", "shortcuts": "Genveje", - "social_description": "Follow us on social media to stay updated with the latest news, updates and releases.", - "social_links": "Social links", + "social_description": "Følg os på sociale medier for at holde dig opdateret med de seneste nyheder, opdateringer og udgivelser.", + "social_links": "Sociale links", "spotlight": "Spotlight", "status": "Status", - "status_description": "Check the status of the website", - "terms_and_privacy": "Vilkår og fortrolighed", + "status_description": "Tjek hjemmesidens status", + "terms_and_privacy": "Vilkår og privatlivspolitik", "twitter": "Twitter", - "type_a_command_search": "Skriv en kommando eller søg ...", + "type_a_command_search": "Skriv en kommando eller søg...", "we_use_cookies": "Vi bruger cookies", + "updated_text": "Hoppscotch er blevet opdateret til v{version} 🎉", "whats_new": "Hvad er nyt?", + "see_whats_new": "Se hvad der er nyt", "wiki": "Wiki" }, "auth": { - "account_exists": "Kontoen findes med forskellige legitimationsoplysninger - Log ind for at linke begge konti", - "all_sign_in_options": "Alle muligheder for login", - "continue_with_email": "Fortsæt med e -mail", + "account_exists": "Konto eksisterer med andre legitimationsoplysninger - Log ind for at forbinde begge konti", + "all_sign_in_options": "Alle loginmuligheder", + "continue_with_auth_provider": "Fortsæt med {provider}", + "continue_with_email": "Fortsæt med e-mail", "continue_with_github": "Fortsæt med GitHub", + "continue_with_github_enterprise": "Fortsæt med GitHub Enterprise", "continue_with_google": "Fortsæt med Google", - "continue_with_microsoft": "Continue with Microsoft", + "continue_with_microsoft": "Fortsæt med Microsoft", "continue_with_oidc": "Fortsæt med OIDC", - "email": "E -mail", + "email": "E-mail", "logged_out": "Logget ud", - "login": "Log på", - "login_success": "Logget ind", + "login": "Log ind", + "login_success": "Logget ind med succes", "login_to_hoppscotch": "Log ind på Hoppscotch", "logout": "Log ud", - "re_enter_email": "Genindtast email", + "re_enter_email": "Indtast e-mail igen", "send_magic_link": "Send et magisk link", - "sync": "Synkronisere", + "sync": "Synkroniser", "we_sent_magic_link": "Vi har sendt dig et magisk link!", - "we_sent_magic_link_description": "Tjek din indbakke - vi sendte en e -mail til {email}. Den indeholder et magisk link, der logger dig ind." + "we_sent_magic_link_description": "Tjek din indbakke - vi har sendt en e-mail til {email}. Den indeholder et magisk link, der vil logge dig ind." }, "authorization": { - "generate_token": "Generer Token", - "graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", + "generate_token": "Generer token", + "graphql_headers": "Autorisationsheadere sendes som en del af nyttelasten til connection_init", "include_in_url": "Inkluder i URL", - "inherited_from": "Inherited from {auth} from Parent Collection {collection} ", + "inherited_from": "Nedarvet {auth} fra overordnet samling {collection}", "learn": "Lær hvordan", "oauth": { - "redirect_auth_server_returned_error": "Auth Server returned an error state", - "redirect_auth_token_request_failed": "Request to get the auth token failed", - "redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token", - "redirect_invalid_state": "Invalid State value present in the redirect", - "redirect_no_auth_code": "No Authorization Code present in the redirect", - "redirect_no_client_id": "No Client ID defined", - "redirect_no_client_secret": "No Client Secret Defined", - "redirect_no_code_verifier": "No Code Verifier Defined", - "redirect_no_token_endpoint": "No Token Endpoint Defined", - "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", - "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "redirect_auth_server_returned_error": "Auth-server returnerede en fejltilstand", + "redirect_auth_token_request_failed": "Anmodning om at få auth-token mislykkedes", + "redirect_auth_token_request_invalid_response": "Ugyldigt svar fra Token-endepunktet ved anmodning om et auth-token", + "redirect_invalid_state": "Ugyldig tilstandsværdi til stede i omdirigeringen", + "redirect_no_auth_code": "Ingen autorisationskode til stede i omdirigeringen", + "redirect_no_client_id": "Ingen klient-id defineret", + "redirect_no_client_secret": "Ingen klienthemmelighed defineret", + "redirect_no_code_verifier": "Ingen kodeverifikator defineret", + "redirect_no_token_endpoint": "Intet token-endepunkt defineret", + "something_went_wrong_on_oauth_redirect": "Noget gik galt under OAuth-omdirigering", + "something_went_wrong_on_token_generation": "Noget gik galt ved tokengenerering", + "token_generation_oidc_discovery_failed": "Fejl ved tokengenerering: OpenID Connect-opdagelse mislykkedes", + "grant_type": "Tilladelsestype", + "grant_type_auth_code": "Autorisationskode", + "token_fetched_successfully": "Token hentet med succes", + "token_fetch_failed": "Kunne ikke hente token", + "validation_failed": "Validering mislykkedes, tjek venligst formularfelterne", + "label_authorization_endpoint": "Autorisationsendepunkt", + "label_client_id": "Klient-id", + "label_client_secret": "Klienthemmelighed", + "label_code_challenge": "Kodeudfordring", + "label_code_challenge_method": "Kodeudfordringsmetode", + "label_code_verifier": "Kodeverifikator", + "label_scopes": "Områder", + "label_token_endpoint": "Token-endepunkt", + "label_use_pkce": "Brug PKCE", + "label_implicit": "Implicit", + "label_password": "Adgangskode", + "label_username": "Brugernavn", + "label_auth_code": "Autorisationskode", + "label_client_credentials": "Klientlegitimationsoplysninger" }, - "pass_key_by": "Pass by", + "pass_key_by": "Send via", + "pass_by_query_params_label": "Forespørgselsparametre", + "pass_by_headers_label": "Headers", "password": "Adgangskode", - "save_to_inherit": "Please save this request in any collection to inherit the authorization", - "token": "Polet", - "type": "Godkendelse Type", + "save_to_inherit": "Gem venligst denne anmodning i en samling for at arve autorisationen", + "token": "Token", + "type": "Autorisationstype", "username": "Brugernavn" }, "collection": { - "created": "Samlingen er oprettet", - "different_parent": "Cannot reorder collection with different parent", - "edit": "Rediger samling", - "import_or_create": "Import or create a collection", - "invalid_name": "Angiv et gyldigt navn til samlingen", - "invalid_root_move": "Collection already in the root", - "moved": "Moved Successfully", - "my_collections": "Mine samlinger", + "created": "Samling oprettet", + "different_parent": "Kan ikke omarrangere samling med forskellig overordnet", + "edit": "Redigér samling", + "import_or_create": "Importér eller opret en samling", + "import_collection": "Importér samling", + "invalid_name": "Angiv venligst et navn til samlingen", + "invalid_root_move": "Samling allerede i roden", + "moved": "Flyttet med succes", + "my_collections": "Personlige samlinger", "name": "Min nye samling", - "name_length_insufficient": "Collection name should be at least 3 characters long", - "new": "Ny kollektion", - "order_changed": "Collection Order Updated", - "properties": "Collection Properties", - "properties_updated": "Collection Properties Updated", + "name_length_insufficient": "Samlingens navn skal være mindst 3 tegn langt", + "new": "Ny samling", + "order_changed": "Samlingsrækkefølge opdateret", + "properties": "Samlingsegenskaber", + "properties_updated": "Samlingsegenskaber opdateret", "renamed": "Samling omdøbt", - "request_in_use": "Request in use", + "request_in_use": "Anmodning i brug", "save_as": "Gem som", - "save_to_collection": "Save to Collection", + "save_to_collection": "Gem i samling", "select": "Vælg en samling", "select_location": "Vælg placering", - "select_team": "Vælg et hold", - "team_collections": "Teamsamlinger" + "details": "Detaljer", + "duplicated": "Samling duplikeret" }, "confirm": { - "close_unsaved_tab": "Are you sure you want to close this tab?", - "close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", - "exit_team": "Are you sure you want to leave this team?", - "logout": "Er du sikker på, at du vil logge af?", - "remove_collection": "Er du sikker på, at du vil slette denne samling permanent?", - "remove_environment": "Er du sikker på, at du vil slette dette miljø permanent?", - "remove_folder": "Er du sikker på, at du vil slette denne mappe permanent?", - "remove_history": "Er du sikker på, at du vil slette hele historikken permanent?", - "remove_request": "Er du sikker på, at du vil slette denne anmodning permanent?", - "remove_shared_request": "Are you sure you want to permanently delete this shared request?", - "remove_team": "Er du sikker på, at du vil slette dette hold?", + "close_unsaved_tab": "Er du sikker på, at du vil lukke denne fane?", + "close_unsaved_tabs": "Er du sikker på, at du vil lukke alle faner? {count} ikke-gemte faner vil gå tabt.", + "exit_team": "Er du sikker på, at du vil forlade dette arbejdsområde?", + "logout": "Er du sikker på, at du vil logge ud?", + "remove_collection": "Er du sikker på, at du permanent vil slette denne samling?", + "remove_environment": "Er du sikker på, at du permanent vil slette dette miljø?", + "remove_folder": "Er du sikker på, at du permanent vil slette denne mappe?", + "remove_history": "Er du sikker på, at du permanent vil slette al historik?", + "remove_request": "Er du sikker på, at du permanent vil slette denne anmodning?", + "remove_shared_request": "Er du sikker på, at du permanent vil slette denne delte anmodning?", + "remove_team": "Er du sikker på, at du vil slette dette arbejdsområde?", "remove_telemetry": "Er du sikker på, at du vil fravælge telemetri?", - "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", - "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Er du sikker på, at du vil synkronisere dette arbejdsområde?" + "request_change": "Er du sikker på, at du vil kassere den nuværende anmodning, ikke-gemte ændringer vil gå tabt.", + "save_unsaved_tab": "Vil du gemme ændringer foretaget i denne fane?", + "sync": "Vil du gendanne dit arbejdsområde fra skyen? Dette vil kassere dit lokale fremskridt.", + "delete_access_token": "Er du sikker på, at du vil slette adgangstokenet {tokenLabel}?" }, "context_menu": { - "add_parameters": "Add to parameters", - "open_request_in_new_tab": "Open request in new tab", - "set_environment_variable": "Set as variable" + "add_parameters": "Tilføj til parametre", + "open_request_in_new_tab": "Åbn anmodning i ny fane", + "set_environment_variable": "Indstil som variabel" }, "cookies": { "modal": { - "cookie_expires": "Expires", - "cookie_name": "Name", - "cookie_path": "Path", - "cookie_string": "Cookie string", - "cookie_value": "Value", - "empty_domain": "Domain is empty", - "empty_domains": "Domain list is empty", - "enter_cookie_string": "Enter cookie string", - "interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.", - "managed_tab": "Managed", - "new_domain_name": "New domain name", - "no_cookies_in_domain": "No cookies set for this domain", - "raw_tab": "Raw", - "set": "Set a cookie" + "cookie_expires": "Udløber", + "cookie_name": "Navn", + "cookie_path": "Sti", + "cookie_string": "Cookie-streng", + "cookie_value": "Værdi", + "empty_domain": "Domæne er tomt", + "empty_domains": "Domæneliste er tom", + "enter_cookie_string": "Indtast cookie-streng", + "interceptor_no_support": "Din aktuelt valgte interceptor understøtter ikke cookies. Vælg en anden interceptor og prøv igen.", + "managed_tab": "Administreret", + "new_domain_name": "Nyt domænenavn", + "no_cookies_in_domain": "Ingen cookies indstillet for dette domæne", + "raw_tab": "Rå", + "set": "Indstil en cookie" } }, "count": { - "header": "Overskrift {count}", + "header": "Header {count}", "message": "Besked {count}", "parameter": "Parameter {count}", "protocol": "Protokol {count}", @@ -219,354 +249,378 @@ }, "documentation": { "generate": "Generer dokumentation", - "generate_message": "Importer enhver Hoppscotch-samling for at generere API-dokumentation, når du er på farten." + "generate_message": "Importér enhver Hoppscotch-samling for at generere API-dokumentation on-the-go." }, "empty": { - "authorization": "Denne anmodning anvender ikke nogen autorisation", - "body": "Denne anmodning har ikke et organ", + "authorization": "Denne anmodning bruger ingen autorisation", + "body": "Denne anmodning har ingen meddelelse", "collection": "Samlingen er tom", "collections": "Samlinger er tomme", - "documentation": "Connect to a GraphQL endpoint to view documentation", - "endpoint": "Endpoint cannot be empty", + "documentation": "Forbind til et GraphQL-endepunkt for at se dokumentation", + "endpoint": "Endepunkt kan ikke være tomt", "environments": "Miljøer er tomme", - "folder": "Mappen er tom", - "headers": "Denne anmodning har ingen overskrifter", - "history": "Historien er tom", - "invites": "Invite list is empty", - "members": "Holdet er tomt", + "folder": "Mappe er tom", + "headers": "Denne anmodning har ingen headers", + "history": "Historik er tom", + "invites": "Invitationsliste er tom", + "members": "Arbejdsområde er tomt", "parameters": "Denne anmodning har ingen parametre", - "pending_invites": "There are no pending invites for this team", - "profile": "Login to view your profile", + "pending_invites": "Der er ingen ventende invitationer til dette arbejdsområde", + "profile": "Log ind for at se din profil", "protocols": "Protokoller er tomme", - "schema": "Opret forbindelse til et GraphQL -slutpunkt", - "shared_requests": "Shared requests are empty", - "shared_requests_logout": "Login to view your shared requests or create a new one", - "subscription": "Subscriptions are empty", - "team_name": "Teamnavn er tomt", - "teams": "Hold er tomme", - "tests": "Der er ingen test for denne anmodning", - "shortcodes": "Shortcodes are empty" + "request_variables": "Denne anmodning har ingen anmodningsvariabler", + "schema": "Forbind til et GraphQL-endepunkt for at se skema", + "secret_environments": "Hemmeligheder synkroniseres ikke til Hoppscotch", + "shared_requests": "Delte anmodninger er tomme", + "shared_requests_logout": "Log ind for at se dine delte anmodninger eller opret en ny", + "subscription": "Abonnementer er tomme", + "team_name": "Arbejdsområdenavn tomt", + "teams": "Du tilhører ikke nogen arbejdsområder", + "tests": "Der er ingen tests for denne anmodning", + "access_tokens": "Adgangstokens er tomme" }, "environment": { - "add_to_global": "Add to Global", - "added": "Environment addition", - "create_new": "Skab nyt miljø", - "created": "Environment created", - "deleted": "Environment deletion", - "duplicated": "Environment duplicated", - "edit": "Rediger miljø", - "empty_variables": "No variables", + "add_to_global": "Tilføj til global", + "added": "Miljø tilføjet", + "create_new": "Opret nyt miljø", + "created": "Miljø oprettet", + "deleted": "Miljø slettet", + "duplicated": "Miljø duplikeret", + "edit": "Redigér miljø", + "empty_variables": "Ingen variabler", "global": "Global", - "global_variables": "Global variables", - "import_or_create": "Import or create a environment", - "invalid_name": "Angiv et gyldigt navn på miljøet", - "list": "Environment variables", - "my_environments": "My Environments", - "name": "Name", - "nested_overflow": "nested environment variables are limited to 10 levels", + "global_variables": "Globale variabler", + "import_or_create": "Importér eller opret et miljø", + "invalid_name": "Angiv venligst et navn til miljøet", + "list": "Miljøvariabler", + "my_environments": "Personlige miljøer", + "name": "Navn", + "nested_overflow": "Indlejrede miljøvariabler er begrænset til 10 niveauer", "new": "Nyt miljø", - "no_active_environment": "No active environment", + "no_active_environment": "Intet aktivt miljø", "no_environment": "Intet miljø", - "no_environment_description": "No environments were selected. Choose what to do with the following variables.", - "quick_peek": "Environment Quick Peek", - "replace_with_variable": "Replace with variable", - "scope": "Scope", + "no_environment_description": "Ingen miljøer blev valgt. Vælg hvad der skal gøres med følgende variabler.", + "quick_peek": "Miljø hurtig kig", + "replace_with_variable": "Erstat med variabel", + "scope": "Omfang", + "secrets": "Hemmeligheder", + "secret_value": "Hemmelig værdi", "select": "Vælg miljø", - "set": "Set environment", - "set_as_environment": "Set as environment", - "team_environments": "Team Environments", + "set": "Indstil miljø", + "set_as_environment": "Indstil som miljø", + "team_environments": "Arbejdsområdemiljøer", "title": "Miljøer", - "updated": "Environment updation", - "value": "Value", - "variable": "Variable", - "variable_list": "Variabel liste" + "updated": "Miljø opdateret", + "value": "Værdi", + "variable": "Variabel", + "variables": "Variabler", + "variable_list": "Variabelliste", + "properties": "Miljøegenskaber", + "details": "Detaljer" }, "error": { - "authproviders_load_error": "Unable to load auth providers", - "browser_support_sse": "Det ser ikke ud til, at denne browser understøtter Server Sent Events.", - "check_console_details": "Tjek konsollog for at få flere oplysninger.", - "check_how_to_add_origin": "Check how you can add an origin", + "authproviders_load_error": "Kunne ikke indlæse autorisationsudbydere", + "browser_support_sse": "Denne browser ser ikke ud til at have understøttelse af Server Sent Events.", + "check_console_details": "Tjek konsolloggen for detaljer.", + "check_how_to_add_origin": "Tjek hvordan du kan tilføje en oprindelse", "curl_invalid_format": "cURL er ikke formateret korrekt", - "danger_zone": "Danger zone", - "delete_account": "Your account is currently an owner in these teams:", - "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", - "empty_req_name": "Tom anmodningsnavn", + "danger_zone": "Farligt område", + "delete_account": "Din konto er i øjeblikket ejer af disse arbejdsområder:", + "delete_account_description": "Du skal enten fjerne dig selv, overføre ejerskab eller slette disse arbejdsområder, før du kan slette din konto.", + "empty_profile_name": "Profilnavn kan ikke være tomt", + "empty_req_name": "Tomt anmodningsnavn", "f12_details": "(F12 for detaljer)", - "gql_prettify_invalid_query": "Kunne ikke prætificere en ugyldig forespørgsel, løse forespørgselssyntaksfejl og prøve igen", - "incomplete_config_urls": "Incomplete configuration URLs", - "incorrect_email": "Incorrect email", - "invalid_link": "Invalid link", - "invalid_link_description": "The link you clicked is invalid or expired.", - "json_parsing_failed": "Invalid JSON", - "json_prettify_invalid_body": "Kunne ikke pryde et ugyldigt brødtekst, løse json -syntaksfejl og prøve igen", - "network_error": "There seems to be a network error. Please try again.", - "network_fail": "Anmodningen kunne ikke sendes", - "no_collections_to_export": "No collections to export. Please create a collection to get started.", + "gql_prettify_invalid_query": "Kunne ikke forskønne en ugyldig forespørgsel, løs forespørgselssyntaksfejl og prøv igen", + "incomplete_config_urls": "Ufuldstændige konfigurationsadresser", + "incorrect_email": "Forkert e-mail", + "invalid_link": "Ugyldigt link", + "invalid_link_description": "Linket, du klikkede på, er ugyldigt eller udløbet.", + "invalid_embed_link": "Indlejringen eksisterer ikke eller er ugyldig.", + "json_parsing_failed": "Ugyldig JSON", + "json_prettify_invalid_body": "Kunne ikke forskønne en ugyldig meddelelse, løs json-syntaksfejl og prøv igen", + "network_error": "Der ser ud til at være en netværksfejl. Prøv venligst igen.", + "network_fail": "Kunne ikke sende anmodning", + "no_collections_to_export": "Ingen samlinger at eksportere. Opret venligst en samling for at komme i gang.", "no_duration": "Ingen varighed", - "no_environments_to_export": "No environments to export. Please create an environment to get started.", - "no_results_found": "No matches found", - "page_not_found": "This page could not be found", - "please_install_extension": "Please install the extension and add origin to the extension.", - "proxy_error": "Proxy error", + "no_environments_to_export": "Ingen miljøer at eksportere. Opret venligst et miljø for at komme i gang.", + "no_results_found": "Ingen matches fundet", + "page_not_found": "Denne side kunne ikke findes", + "please_install_extension": "Installer venligst udvidelsen og tilføj oprindelse til udvidelsen.", + "proxy_error": "Proxy-fejl", + "same_profile_name": "Opdateret profilnavn er det samme som det nuværende profilnavn", "script_fail": "Kunne ikke udføre pre-request script", "something_went_wrong": "Noget gik galt", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Kunne ikke udføre post-request script", + "reading_files": "Fejl under læsning af en eller flere filer.", + "fetching_access_tokens_list": "Noget gik galt under hentning af listen over tokens", + "generate_access_token": "Noget gik galt under generering af adgangstoken", + "delete_access_token": "Noget gik galt under sletning af adgangstoken" }, "export": { - "as_json": "Eksporter som JSON", + "as_json": "Eksportér som JSON", "create_secret_gist": "Opret hemmelig Gist", - "failed": "Something went wrong while exporting", - "gist_created": "Gist skabt", - "require_github": "Log ind med GitHub for at skabe hemmelig kerne", - "title": "Export" + "create_secret_gist_tooltip_text": "Eksportér som hemmelig Gist", + "failed": "Noget gik galt under eksport", + "secret_gist_success": "Eksporteret som hemmelig Gist med succes", + "require_github": "Log ind med GitHub for at oprette hemmelig gist", + "title": "Eksportér", + "success": "Eksporteret med succes" }, "filter": { - "all": "All", - "none": "None", - "starred": "Starred" + "all": "Alle", + "none": "Ingen", + "starred": "Stjernemarkeret" }, "folder": { "created": "Mappe oprettet", - "edit": "Rediger mappe", - "invalid_name": "Angiv et navn til mappen", - "name_length_insufficient": "Folder name should be at least 3 characters long", + "edit": "Redigér mappe", + "invalid_name": "Angiv venligst et navn til mappen", + "name_length_insufficient": "Mappenavn skal være mindst 3 tegn langt", "new": "Ny mappe", - "renamed": "Mappen omdøbt" + "renamed": "Mappe omdøbt" }, "graphql": { - "connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?", - "connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is", - "connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is", + "connection_switch_confirm": "Vil du forbinde med det seneste GraphQL-endepunkt?", + "connection_switch_new_url": "Skift til en fane vil afbryde din forbindelse til den aktive GraphQL-forbindelse. Ny forbindelsesadresse er", + "connection_switch_url": "Du er forbundet til et GraphQL-endepunkt, forbindelsesadressen er", "mutations": "Mutationer", "schema": "Skema", "subscriptions": "Abonnementer", - "switch_connection": "Switch connection" + "switch_connection": "Skift forbindelse", + "url_placeholder": "Indtast en GraphQL-endepunktsadresse" }, "graphql_collections": { - "title": "GraphQL Collections" + "title": "GraphQL-samlinger" }, "group": { - "time": "Time", + "time": "Tid", "url": "URL" }, "header": { - "install_pwa": "Installer app", - "login": "Log på", + "install_pwa": "Installér app", + "login": "Log ind", "save_workspace": "Gem mit arbejdsområde" }, "helpers": { - "authorization": "Autorisationsoverskriften genereres automatisk, når du sender anmodningen.", - "collection_properties_authorization": " This authorization will be set for every request in this collection.", - "collection_properties_header": "This header will be set for every request in this collection.", - "generate_documentation_first": "Generer først dokumentation", - "network_fail": "Kunne ikke nå API -slutpunktet. Kontroller din netværksforbindelse, og prøv igen.", - "offline": "Du ser ud til at være offline. Data i dette arbejdsområde er muligvis ikke opdaterede.", - "offline_short": "Du ser ud til at være offline.", - "post_request_tests": "Test scripts er skrevet i JavaScript og køres efter at svaret er modtaget.", - "pre_request_script": "Forhåndsanmodnings scripts er skrevet i JavaScript og køres før anmodningen sendes.", - "script_fail": "Det ser ud til, at der er en fejl i pre-request-scriptet. Tjek fejlen nedenfor, og ret scriptet i overensstemmelse hermed.", - "test_script_fail": "There seems to be an error with test script. Please fix the errors and run tests again", - "tests": "Skriv et test script for at automatisere fejlfinding." + "authorization": "Autorisationsheaderen vil automatisk blive genereret, når du sender anmodningen.", + "collection_properties_authorization": "Denne autorisation vil blive indstillet for hver anmodning i denne samling.", + "collection_properties_header": "Denne header vil blive indstillet for hver anmodning i denne samling.", + "generate_documentation_first": "Generer dokumentation først", + "network_fail": "Kan ikke nå API-endepunktet. Tjek din netværksforbindelse eller vælg en anden Interceptor og prøv igen.", + "offline": "Du bruger Hoppscotch offline. Opdateringer vil synkronisere, når du er online, baseret på arbejdsområdeindstillinger.", + "offline_short": "Du bruger Hoppscotch offline.", + "post_request_tests": "Testscripts er skrevet i JavaScript og køres efter svaret er modtaget.", + "pre_request_script": "Pre-request scripts er skrevet i JavaScript og køres før anmodningen sendes.", + "script_fail": "Det ser ud til, at der er en fejl i pre-request scriptet. Tjek fejlen nedenfor og ret scriptet i overensstemmelse hermed.", + "test_script_fail": "Der ser ud til at være en fejl med testscriptet. Ret venligst fejlene og kør tests igen", + "tests": "Skriv et testscript for at automatisere fejlfinding." }, "hide": { - "collection": "Collapse Collection Panel", + "collection": "Skjul samlingspanel", "more": "Skjul mere", "preview": "Skjul forhåndsvisning", - "sidebar": "Skjul sidepanel" + "sidebar": "Skjul sidebjælke" }, "import": { - "collections": "Importer samlinger", - "curl": "Importer cURL", - "environments_from_gist": "Import From Gist", - "environments_from_gist_description": "Import Hoppscotch Environments From Gist", - "failed": "Import mislykkedes", - "from_file": "Import from File", - "from_gist": "Import fra Gist", - "from_gist_description": "Import from Gist URL", - "from_insomnia": "Import from Insomnia", - "from_insomnia_description": "Import from Insomnia collection", - "from_json": "Import from Hoppscotch", - "from_json_description": "Import from Hoppscotch collection file", - "from_my_collections": "Import fra Mine samlinger", - "from_my_collections_description": "Import from My Collections file", - "from_openapi": "Import from OpenAPI", - "from_openapi_description": "Import from OpenAPI specification file (YML/JSON)", - "from_postman": "Import from Postman", - "from_postman_description": "Import from Postman collection", - "from_url": "Import from URL", - "gist_url": "Indtast Gist URL", - "gql_collections_from_gist_description": "Import GraphQL Collections From Gist", - "hoppscotch_environment": "Hoppscotch Environment", - "hoppscotch_environment_description": "Import Hoppscotch Environment JSON file", - "import_from_url_invalid_fetch": "Couldn't get data from the url", - "import_from_url_invalid_file_format": "Error while importing collections", - "import_from_url_invalid_type": "Unsupported type. accepted values are 'hoppscotch', 'openapi', 'postman', 'insomnia'", - "import_from_url_success": "Collections Imported", - "insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file", - "json_description": "Import collections from a Hoppscotch Collections JSON file", - "postman_environment": "Postman Environment", - "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Importere" + "collections": "Importér samlinger", + "curl": "Importér cURL", + "environments_from_gist": "Importér fra Gist", + "environments_from_gist_description": "Importér Hoppscotch-miljøer fra Gist", + "failed": "Fejl under import: format ikke genkendt", + "from_file": "Importér fra fil", + "from_gist": "Importér fra Gist", + "from_gist_description": "Importér fra Gist-URL", + "from_insomnia": "Importér fra Insomnia", + "from_insomnia_description": "Importér fra Insomnia-samling", + "from_json": "Importér fra Hoppscotch", + "from_json_description": "Importér fra Hoppscotch-samlingsfil", + "from_my_collections": "Importér fra personlige samlinger", + "from_my_collections_description": "Importér fra personlige samlingsfil", + "from_openapi": "Importér fra OpenAPI", + "from_openapi_description": "Importér fra OpenAPI-specifikationsfil (YML/JSON)", + "from_postman": "Importér fra Postman", + "from_postman_description": "Importér fra Postman-samling", + "from_url": "Importér fra URL", + "gist_url": "Indtast Gist-URL", + "gql_collections_from_gist_description": "Importér GraphQL-samlinger fra Gist", + "hoppscotch_environment": "Hoppscotch-miljø", + "hoppscotch_environment_description": "Importér Hoppscotch-miljø JSON-fil", + "import_from_url_invalid_fetch": "Kunne ikke hente data fra URL'en", + "import_from_url_invalid_file_format": "Fejl under import af samlinger", + "import_from_url_invalid_type": "Ikke-understøttet type. Accepterede værdier er 'hoppscotch', 'openapi', 'postman', 'insomnia'", + "import_from_url_success": "Samlinger importeret", + "insomnia_environment_description": "Importér Insomnia-miljø fra en JSON/YAML-fil", + "json_description": "Importér samlinger fra en Hoppscotch-samlinger JSON-fil", + "postman_environment": "Postman-miljø", + "postman_environment_description": "Importér Postman-miljø fra en JSON-fil", + "title": "Importér", + "file_size_limit_exceeded_warning_multiple_files": "Valgte filer overstiger den anbefalede grænse på 10MB. Kun de første {files} valgte vil blive importeret", + "file_size_limit_exceeded_warning_single_file": "Den aktuelt valgte fil overstiger den anbefalede grænse på 10MB. Vælg venligst en anden fil.", + "success": "Importeret med succes" }, "inspections": { - "description": "Inspect possible errors", + "description": "Inspicér mulige fejl", "environment": { - "add_environment": "Add to Environment", - "not_found": "Environment variable “{environment}” not found." + "add_environment": "Tilføj til miljø", + "add_environment_value": "Tilføj værdi", + "empty_value": "Miljøværdi er tom for variablen '{variable}'", + "not_found": "Miljøvariabel \"{environment}\" blev ikke fundet" }, "header": { - "cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." + "cookie": "Browseren tillader ikke Hoppscotch at indstille Cookie-headere. Brug venligst autorisationsheadere i stedet. Vores Hoppscotch Desktop App er dog live nu og understøtter cookies." }, "response": { - "401_error": "Please check your authentication credentials.", - "404_error": "Please check your request URL and method type.", - "cors_error": "Please check your Cross-Origin Resource Sharing configuration.", - "default_error": "Please check your request.", - "network_error": "Please check your network connection." + "401_error": "Tjek venligst dine autentifikationsoplysninger.", + "404_error": "Tjek venligst din anmodnings-URL og metodetype.", + "cors_error": "Tjek venligst din Cross-Origin Resource Sharing-konfiguration.", + "default_error": "Tjek venligst din anmodning.", + "network_error": "Tjek venligst din netværksforbindelse." }, - "title": "Inspector", + "title": "Inspektør", "url": { - "extension_not_installed": "Extension not installed.", - "extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", - "extention_enable_action": "Enable Browser Extension", - "extention_not_enabled": "Extension not enabled." + "extension_not_installed": "Udvidelse ikke installeret.", + "extension_unknown_origin": "Sørg for, at du har tilføjet API-endepunktets oprindelse til Hoppscotch Browser Extension-listen.", + "extention_enable_action": "Aktivér browserudvidelse", + "extention_not_enabled": "Udvidelse ikke aktiveret." } }, "layout": { - "collapse_collection": "Collapse or Expand Collections", - "collapse_sidebar": "Collapse or Expand the sidebar", - "column": "Vertical layout", + "collapse_collection": "Skjul eller udvid samlinger", + "collapse_sidebar": "Skjul eller udvid sidebjælken", + "column": "Lodret layout", "name": "Layout", - "row": "Horizontal layout" + "row": "Vandret layout" }, "modal": { - "close_unsaved_tab": "You have unsaved changes", + "close_unsaved_tab": "Du har ikke-gemte ændringer", "collections": "Samlinger", - "confirm": "Bekræfte", - "customize_request": "Customize Request", - "edit_request": "Rediger anmodning", - "import_export": "Import Eksport", - "share_request": "Share Request" + "confirm": "Bekræft", + "customize_request": "Tilpas anmodning", + "edit_request": "Redigér anmodning", + "import_export": "Import / Eksport", + "share_request": "Del anmodning" }, "mqtt": { - "already_subscribed": "You are already subscribed to this topic.", - "clean_session": "Clean Session", - "clear_input": "Clear input", - "clear_input_on_send": "Clear input on send", - "client_id": "Client ID", - "color": "Pick a color", - "communication": "Meddelelse", - "connection_config": "Connection Config", - "connection_not_authorized": "This MQTT connection does not use any authentication.", - "invalid_topic": "Please provide a topic for the subscription", - "keep_alive": "Keep Alive", + "already_subscribed": "Du abonnerer allerede på dette emne.", + "clean_session": "Ryd session", + "clear_input": "Ryd input", + "clear_input_on_send": "Ryd input ved afsendelse", + "client_id": "Klient-ID", + "color": "Vælg en farve", + "communication": "Kommunikation", + "connection_config": "Forbindelseskonfiguration", + "connection_not_authorized": "Denne MQTT-forbindelse bruger ingen godkendelse.", + "invalid_topic": "Angiv venligst et emne for abonnementet", + "keep_alive": "Hold i live", "log": "Log", - "lw_message": "Last-Will Message", + "lw_message": "Last-Will besked", "lw_qos": "Last-Will QoS", - "lw_retain": "Last-Will Retain", - "lw_topic": "Last-Will Topic", + "lw_retain": "Last-Will behold", + "lw_topic": "Last-Will emne", "message": "Besked", - "new": "New Subscription", - "not_connected": "Please start a MQTT connection first.", - "publish": "Offentliggøre", + "new": "Nyt abonnement", + "not_connected": "Start venligst en MQTT-forbindelse først.", + "publish": "Udgiv", "qos": "QoS", "ssl": "SSL", - "subscribe": "Abonner", + "subscribe": "Abonnér", "topic": "Emne", - "topic_name": "Emne navn", - "topic_title": "Udgiv / Abonner emne", - "unsubscribe": "Opsige abonnement", + "topic_name": "Emnenavn", + "topic_title": "Udgiv / Abonnér på emne", + "unsubscribe": "Afmeld abonnement", "url": "URL" }, "navigation": { - "doc": "Dokumenter", + "doc": "Docs", "graphql": "GraphQL", - "profile": "Profile", + "profile": "Profil", "realtime": "Realtid", "rest": "REST", "settings": "Indstillinger" }, "preRequest": { - "javascript_code": "JavaScript -kode", + "javascript_code": "JavaScript-kode", "learn": "Læs dokumentation", "script": "Pre-Request Script", - "snippets": "Uddrag" + "snippets": "Snippets" }, "profile": { - "app_settings": "App Settings", - "default_hopp_displayname": "Unnamed User", - "editor": "Editor", - "editor_description": "Editors can add, edit, and delete requests.", - "email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.", - "no_permission": "You do not have permission to perform this action.", - "owner": "Owner", - "owner_description": "Owners can add, edit, and delete requests, collections and team members.", - "roles": "Roles", - "roles_description": "Roles are used to control access to the shared collections.", - "updated": "Profile updated", - "viewer": "Viewer", - "viewer_description": "Viewers can only view and use requests." + "app_settings": "App-indstillinger", + "default_hopp_displayname": "Unavngivet bruger", + "editor": "Redaktør", + "editor_description": "Redaktører kan tilføje, redigere og slette anmodninger.", + "email_verification_mail": "En bekræftelses-e-mail er blevet sendt til din e-mailadresse. Klik venligst på linket for at bekræfte din e-mailadresse.", + "no_permission": "Du har ikke tilladelse til at udføre denne handling.", + "owner": "Ejer", + "owner_description": "Ejere kan tilføje, redigere og slette anmodninger, samlinger og arbejdsområdemedlemmer.", + "roles": "Roller", + "roles_description": "Roller bruges til at kontrollere adgang til de delte samlinger.", + "updated": "Profil opdateret", + "viewer": "Betragter", + "viewer_description": "Betragtere kan kun se og bruge anmodninger." }, "remove": { "star": "Fjern stjerne" }, "request": { "added": "Anmodning tilføjet", - "authorization": "Bemyndigelse", - "body": "Anmodningsorgan", + "authorization": "Autorisation", + "body": "Anmodnings meddelelse", "choose_language": "Vælg sprog", "content_type": "Indholdstype", "content_type_titles": { - "others": "Others", - "structured": "Structured", - "text": "Text" + "others": "Andre", + "structured": "Struktureret", + "text": "Tekst" }, - "different_collection": "Cannot reorder requests from different collections", - "duplicated": "Request duplicated", + "different_collection": "Kan ikke omarrangere anmodninger fra forskellige samlinger", + "duplicated": "Anmodning duplikeret", "duration": "Varighed", - "enter_curl": "Indtast cURL", - "generate_code": "Generer kode", + "enter_curl": "Indtast cURL-kommando", + "generate_code": "Generér kode", "generated_code": "Genereret kode", - "go_to_authorization_tab": "Go to Authorization tab", - "go_to_body_tab": "Go to Body tab", - "header_list": "Overskriftsliste", - "invalid_name": "Angiv et navn på anmodningen", + "go_to_authorization_tab": "Gå til autorisationsfanen", + "go_to_body_tab": "Gå til meddelelsefanen", + "header_list": "Header-liste", + "invalid_name": "Angiv venligst et navn til anmodningen", "method": "Metode", - "moved": "Request moved", + "moved": "Anmodning flyttet", "name": "Anmodningsnavn", - "new": "New Request", - "order_changed": "Request Order Updated", - "override": "Override", - "override_help": "Set Content-Type in Headers", - "overriden": "Overridden", + "new": "Ny anmodning", + "order_changed": "Anmodningsrækkefølge opdateret", + "override": "Tilsidesæt", + "override_help": "Indstil Content-Type i Headers", + "overriden": "Tilsidesat", "parameter_list": "Forespørgselsparametre", "parameters": "Parametre", "path": "Sti", "payload": "Nyttelast", "query": "Forespørgsel", - "raw_body": "Raw Request Body", - "rename": "Rename Request", + "raw_body": "Rå anmodnings meddelelse", + "rename": "Omdøb anmodning", "renamed": "Anmodning omdøbt", - "run": "Løb", - "save": "Gemme", + "request_variables": "Anmodningsvariabler", + "run": "Kør", + "save": "Gem", "save_as": "Gem som", "saved": "Anmodning gemt", "share": "Del", - "share_description": "Share Hoppscotch with your friends", - "share_request": "Share Request", + "share_description": "Del Hoppscotch med dine venner", + "share_request": "Del anmodning", "stop": "Stop", "title": "Anmodning", "type": "Anmodningstype", "url": "URL", + "url_placeholder": "Indtast en URL eller indsæt en cURL-kommando", "variables": "Variabler", - "view_my_links": "View my links", - "copy_link": "Kopier link" + "view_my_links": "Se mine links", + "generate_name_error": "Kunne ikke generere anmodningsnavn." }, "response": { - "audio": "Audio", - "body": "Svarorgan", - "filter_response_body": "Filter JSON response body (uses JSONPath syntax)", - "headers": "Overskrifter", + "audio": "Lyd", + "body": "Svarbesked", + "filter_response_body": "Filtrer JSON-svarbesked (bruger JSONPath-syntaks)", + "headers": "Headers", "html": "HTML", "image": "Billede", "json": "JSON", @@ -576,242 +630,249 @@ "size": "Størrelse", "status": "Status", "time": "Tid", - "title": "Respons", + "title": "Svar", "video": "Video", "waiting_for_connection": "venter på forbindelse", "xml": "XML" }, "settings": { - "accent_color": "Accent farve", + "accent_color": "Accentfarve", "account": "Konto", - "account_deleted": "Your account has been deleted", + "account_deleted": "Din konto er blevet slettet", "account_description": "Tilpas dine kontoindstillinger.", - "account_email_description": "Din primære e -mail -adresse.", + "account_email_description": "Din primære e-mailadresse.", "account_name_description": "Dette er dit visningsnavn.", - "additional": "Additional Settings", + "additional": "Yderligere indstillinger", "background": "Baggrund", "black_mode": "Sort", "choose_language": "Vælg sprog", "dark_mode": "Mørk", - "delete_account": "Delete account", - "delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.", - "expand_navigation": "Expand navigation", + "delete_account": "Slet konto", + "delete_account_description": "Når du sletter din konto, vil alle dine data blive permanent slettet. Denne handling kan ikke fortrydes.", + "expand_navigation": "Udvid navigation", "experiments": "Eksperimenter", - "experiments_notice": "Dette er en samling af eksperimenter, vi arbejder på, der kan vise sig at være nyttige, sjove, begge dele eller ingen af dem. De er ikke endelige og er muligvis ikke stabile, så hvis der sker noget alt for mærkeligt, skal du ikke gå i panik. Bare sluk for dang -tingen. Vittigheder til side,", + "experiments_notice": "Dette er en samling af eksperimenter, vi arbejder på, som måske viser sig at være nyttige, sjove, begge dele eller ingen af delene. De er ikke endelige og er måske ikke stabile, så hvis noget overdrevent mærkeligt sker, så gå ikke i panik. Bare sluk for det. Spøg til side, ", "extension_ver_not_reported": "Ikke rapporteret", - "extension_version": "Udvidelsesversion", - "extensions": "Udvidelser", - "extensions_use_toggle": "Brug browserudvidelsen til at sende anmodninger (hvis de findes)", - "follow": "Follow Us", - "interceptor": "Aflytter", + "extension_version": "Udvidelses version", + "extensions": "Browserudvidelse", + "extensions_use_toggle": "Brug browserudvidelsen til at sende anmodninger (hvis til stede)", + "follow": "Følg os", + "interceptor": "Interceptor", "interceptor_description": "Middleware mellem applikation og API'er.", "language": "Sprog", "light_mode": "Lys", - "official_proxy_hosting": "Officiel proxy er hostet af Hoppscotch.", - "profile": "Profile", - "profile_description": "Update your profile details", - "profile_email": "Email address", - "profile_name": "Profile name", + "official_proxy_hosting": "Officiel Proxy hostes af Hoppscotch.", + "profile": "Profil", + "profile_description": "Opdater dine profildetaljer", + "profile_email": "E-mailadresse", + "profile_name": "Profilnavn", "proxy": "Proxy", - "proxy_url": "Proxy -URL", - "proxy_use_toggle": "Brug proxy -middleware til at sende anmodninger", + "proxy_url": "Proxy URL", + "proxy_use_toggle": "Brug proxy-middleware til at sende anmodninger", "read_the": "Læs", "reset_default": "Nulstil til standard", - "short_codes": "Short codes", - "short_codes_description": "Short codes which were created by you.", - "sidebar_on_left": "Sidebar on left", + "short_codes": "Korte koder", + "short_codes_description": "Korte koder, som blev oprettet af dig.", + "sidebar_on_left": "Sidebjælke til venstre", + "ai_experiments": "AI-eksperimenter", "sync": "Synkroniser", "sync_collections": "Samlinger", - "sync_description": "Disse indstillinger er synkroniseret til cloud.", + "sync_description": "Disse indstillinger synkroniseres til skyen.", "sync_environments": "Miljøer", - "sync_history": "Historie", + "sync_history": "Historik", "system_mode": "System", "telemetry": "Telemetri", - "telemetry_helps_us": "Telemetri hjælper os med at tilpasse vores drift og levere den bedste oplevelse til dig.", + "telemetry_helps_us": "Telemetri hjælper os med at personliggøre vores operationer og levere den bedste oplevelse til dig.", "theme": "Tema", "theme_description": "Tilpas dit applikationstema.", - "use_experimental_url_bar": "Brug eksperimentel URL -bjælke med miljøfremhævning", + "use_experimental_url_bar": "Brug eksperimentel URL-bar med miljøfremhævning", "user": "Bruger", - "verified_email": "Verified email", - "verify_email": "Verify email" + "verified_email": "Bekræftet e-mail", + "verify_email": "Bekræft e-mail" }, "shared_requests": { - "button": "Button", - "button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.", - "copy_html": "Copy HTML", - "copy_link": "Copy Link", - "copy_markdown": "Copy Markdown", - "creating_widget": "Creating widget", - "customize": "Customize", - "deleted": "Shared request deleted", - "description": "Select a widget, you can change and customize this later", - "embed": "Embed", - "embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.", + "button": "Knap", + "button_info": "Opret en 'Kør i Hoppscotch' knap til dit websted, blog eller en README.", + "copy_html": "Kopiér HTML", + "copy_link": "Kopiér link", + "copy_markdown": "Kopiér Markdown", + "creating_widget": "Opretter widget", + "customize": "Tilpas", + "deleted": "Delt anmodning slettet", + "description": "Vælg en widget, du kan ændre og tilpasse dette senere", + "embed": "Indlejr", + "embed_info": "Tilføj en mini 'Hoppscotch API Playground' til dit websted, blog eller dokumentation.", "link": "Link", - "link_info": "Create a shareable link to share with anyone on the internet with view access.", - "modified": "Shared request modified", - "not_found": "Shared request not found", - "open_new_tab": "Open in new tab", - "preview": "Preview", - "run_in_hoppscotch": "Run in Hoppscotch", + "link_info": "Opret et delbart link til at dele med hvem som helst på internettet med visningsadgang.", + "modified": "Delt anmodning ændret", + "not_found": "Delt anmodning ikke fundet", + "open_new_tab": "Åbn i ny fane", + "preview": "Forhåndsvis", + "run_in_hoppscotch": "Kør i Hoppscotch", "theme": { - "dark": "Dark", - "light": "Light", + "dark": "Mørk", + "light": "Lys", "system": "System", - "title": "Theme" + "title": "Tema" } }, "shortcut": { "general": { - "close_current_menu": "Luk den aktuelle menu", + "close_current_menu": "Luk nuværende menu", "command_menu": "Søg & kommandomenu", - "help_menu": "Hjælp menu", + "help_menu": "Hjælpemenu", "show_all": "Tastaturgenveje", - "title": "Generel" + "title": "Generelt" }, "miscellaneous": { - "invite": "Inviter folk til Hoppscotch", + "invite": "Invitér folk til Hoppscotch", "title": "Diverse" }, "navigation": { "back": "Gå tilbage til forrige side", - "documentation": "Gå til siden Dokumentation", - "forward": "Gå videre til næste side", - "graphql": "Gå til GraphQL -siden", - "profile": "Go to Profile page", - "realtime": "Gå til siden Realtid", - "rest": "Gå til REST side", - "settings": "Gå til siden Indstillinger", + "documentation": "Gå til dokumentationssiden", + "forward": "Gå frem til næste side", + "graphql": "Gå til GraphQL-siden", + "profile": "Gå til profilsiden", + "realtime": "Gå til realtidssiden", + "rest": "Gå til REST-siden", + "settings": "Gå til indstillingssiden", "title": "Navigation" }, "others": { - "prettify": "Prettify Editor's Content", - "title": "Others" + "prettify": "Forskøn editorens indhold", + "title": "Andre" }, "request": { - "delete_method": "Vælg SLET metode", - "get_method": "Vælg GET -metode", - "head_method": "Vælg HEAD -metode", - "import_curl": "Import cURL", + "delete_method": "Vælg DELETE-metode", + "get_method": "Vælg GET-metode", + "head_method": "Vælg HEAD-metode", + "import_curl": "Importér cURL", "method": "Metode", - "next_method": "Vælg Næste metode", - "post_method": "Vælg POST -metode", - "previous_method": "Vælg Forrige metode", - "put_method": "Vælg PUT -metode", - "rename": "Rename Request", + "next_method": "Vælg næste metode", + "post_method": "Vælg POST-metode", + "previous_method": "Vælg forrige metode", + "put_method": "Vælg PUT-metode", + "rename": "Omdøb anmodning", "reset_request": "Nulstil anmodning", - "save_request": "Save Request", - "save_to_collections": "Gem i samlinger", + "save_request": "Gem anmodning", + "save_to_collections": "Gem til samlinger", "send_request": "Send anmodning", - "share_request": "Share Request", - "show_code": "Generate code snippet", - "title": "Anmodning", - "copy_request_link": "Kopiér anmodningslink" + "share_request": "Del anmodning", + "show_code": "Generér kodesnippet", + "title": "Anmodning" }, "response": { - "copy": "Copy response to clipboard", - "download": "Download response as file", - "title": "Response" + "copy": "Kopiér svar til udklipsholder", + "download": "Download svar som fil", + "title": "Svar" }, "theme": { - "black": "Switch theme to black mode", - "dark": "Switch theme to dark mode", - "light": "Switch theme to light mode", - "system": "Switch theme to system mode", - "title": "Theme" + "black": "Skift tema til sort tilstand", + "dark": "Skift tema til mørk tilstand", + "light": "Skift tema til lys tilstand", + "system": "Skift tema til systemtilstand", + "title": "Tema" } }, "show": { "code": "Vis kode", - "collection": "Expand Collection Panel", + "collection": "Udvid samlingspanel", "more": "Vis mere", - "sidebar": "Vis sidebjælke" + "sidebar": "Udvid sidebjælke" }, "socketio": { - "communication": "Meddelelse", - "connection_not_authorized": "This SocketIO connection does not use any authentication.", - "event_name": "Begivenhedsnavn", + "communication": "Kommunikation", + "connection_not_authorized": "Denne SocketIO-forbindelse bruger ingen godkendelse.", + "event_name": "Begivenheds-/emnenavn", "events": "Begivenheder", "log": "Log", "url": "URL" }, "spotlight": { - "change_language": "Change Language", + "change_language": "Skift sprog", "environments": { - "delete": "Delete current environment", - "duplicate": "Duplicate current environment", - "duplicate_global": "Duplicate global environment", - "edit": "Edit current environment", - "edit_global": "Edit global environment", - "new": "Create new environment", - "new_variable": "Create a new environment variable", - "title": "Environments" + "delete": "Slet nuværende miljø", + "duplicate": "Duplikér nuværende miljø", + "duplicate_global": "Duplikér globalt miljø", + "edit": "Redigér nuværende miljø", + "edit_global": "Redigér globalt miljø", + "new": "Opret nyt miljø", + "new_variable": "Opret en ny miljøvariabel", + "title": "Miljøer" }, "general": { - "chat": "Chat with support", - "help_menu": "Help and support", - "open_docs": "Read Documentation", - "open_github": "Open GitHub repository", - "open_keybindings": "Keyboard shortcuts", + "chat": "Chat med support", + "help_menu": "Hjælp og support", + "open_docs": "Læs dokumentation", + "open_github": "Åbn GitHub-repository", + "open_keybindings": "Tastaturgenveje", "social": "Social", - "title": "General" + "title": "Generelt" }, "graphql": { - "connect": "Connect to server", - "disconnect": "Disconnect from server" + "connect": "Forbind til server", + "disconnect": "Afbryd forbindelse fra server" }, "miscellaneous": { - "invite": "Invite your friends to Hoppscotch", - "title": "Miscellaneous" + "invite": "Invitér dine venner til Hoppscotch", + "title": "Diverse" }, "request": { - "save_as_new": "Save as new request", - "select_method": "Select method", - "switch_to": "Switch to", - "tab_authorization": "Authorization tab", - "tab_body": "Body tab", - "tab_headers": "Headers tab", - "tab_parameters": "Parameters tab", - "tab_pre_request_script": "Pre-request script tab", - "tab_query": "Query tab", - "tab_tests": "Tests tab", - "tab_variables": "Variables tab" + "save_as_new": "Gem som ny anmodning", + "select_method": "Vælg metode", + "switch_to": "Skift til", + "tab_authorization": "Autorisationsfane", + "tab_body": "Meddelelsefane", + "tab_headers": "Headers-fane", + "tab_parameters": "Parameterfane", + "tab_pre_request_script": "Pre-request script-fane", + "tab_query": "Forespørgselsfane", + "tab_tests": "Testfane", + "tab_variables": "Variabelfane" }, "response": { - "copy": "Copy response", - "download": "Download response as file", - "title": "Response" + "copy": "Kopiér svar", + "download": "Download svar som fil", + "title": "Svar" }, "section": { "interceptor": "Interceptor", - "interface": "Interface", - "theme": "Theme", - "user": "User" + "interface": "Grænseflade", + "theme": "Tema", + "user": "Bruger" }, "settings": { - "change_interceptor": "Change Interceptor", - "change_language": "Change Language", + "change_interceptor": "Skift interceptor", + "change_language": "Skift sprog", "theme": { - "black": "Black", - "dark": "Dark", - "light": "Light", - "system": "System preference" + "black": "Sort", + "dark": "Mørk", + "light": "Lys", + "system": "Systemindstilling" } }, "tab": { - "close_current": "Close current tab", - "close_others": "Close all other tabs", - "duplicate": "Duplicate current tab", - "new_tab": "Open a new tab", - "title": "Tabs" + "close_current": "Luk nuværende fane", + "close_others": "Luk alle andre faner", + "duplicate": "Duplikér nuværende fane", + "new_tab": "Åbn en ny fane", + "title": "Faner" }, "workspace": { - "delete": "Delete current team", - "edit": "Edit current team", - "invite": "Invite people to team", - "new": "Create new team", - "switch_to_personal": "Switch to your personal workspace", - "title": "Teams" + "delete": "Slet nuværende arbejdsområde", + "edit": "Redigér nuværende arbejdsområde", + "invite": "Invitér folk til arbejdsområde", + "new": "Opret nyt arbejdsområde", + "switch_to_personal": "Skift til dit personlige arbejdsområde", + "title": "Arbejdsområder" + }, + "phrases": { + "try": "Prøv", + "import_collections": "Importér samlinger", + "create_environment": "Opret miljø", + "create_workspace": "Opret arbejdsområde", + "share_request": "Del anmodning" } }, "sse": { @@ -821,43 +882,43 @@ }, "state": { "bulk_mode": "Masseredigering", - "bulk_mode_placeholder": "Indlæg adskilles med ny linje\nNøgler og værdier adskilles af:\nForbered # til enhver række, du vil tilføje, men behold den deaktiveret", + "bulk_mode_placeholder": "Poster er adskilt af linjeskift\nNøgler og værdier er adskilt af :\nIndled med # for enhver række, du ønsker at tilføje, men holde deaktiveret", "cleared": "Ryddet", - "connected": "Tilsluttet", + "connected": "Forbundet", "connected_to": "Forbundet til {name}", - "connecting_to": "Opretter forbindelse til {name} ...", - "connection_error": "Failed to connect", - "connection_failed": "Connection failed", - "connection_lost": "Connection lost", - "copied_interface_to_clipboard": "Copied {language} interface type to clipboard", + "connecting_to": "Forbinder til {name}...", + "connection_error": "Kunne ikke oprette forbindelse", + "connection_failed": "Forbindelse mislykkedes", + "connection_lost": "Forbindelse tabt", + "copied_interface_to_clipboard": "Kopierede {language} interfacetype til udklipsholder", "copied_to_clipboard": "Kopieret til udklipsholder", "deleted": "Slettet", - "deprecated": "DEPRECATED", - "disabled": "handicappet", + "deprecated": "FORÆLDET", + "disabled": "Deaktiveret", "disconnected": "Afbrudt", - "disconnected_from": "Koblet fra {name}", + "disconnected_from": "Afbrudt fra {name}", "docs_generated": "Dokumentation genereret", - "download_failed": "Download failed", - "download_started": "Downloaden er startet", + "download_failed": "Download mislykkedes", + "download_started": "Download startet", "enabled": "Aktiveret", "file_imported": "Fil importeret", - "finished_in": "Færdig om {duration} ms", - "hide": "Hide", + "finished_in": "Afsluttet på {duration} ms", + "hide": "Skjul", "history_deleted": "Historik slettet", - "linewrap": "Wrap linjer", + "linewrap": "Ombryd linjer", "loading": "Indlæser...", - "message_received": "Message: {message} arrived on topic: {topic}", - "mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}", + "message_received": "Besked: {message} ankom på emne: {topic}", + "mqtt_subscription_failed": "Noget gik galt under abonnement på emne: {topic}", "none": "Ingen", - "nothing_found": "Intet fundet til", - "published_error": "Something went wrong while publishing msg: {topic} to topic: {message}", - "published_message": "Published message: {message} to topic: {topic}", - "reconnection_error": "Failed to reconnect", - "show": "Show", - "subscribed_failed": "Failed to subscribe to topic: {topic}", - "subscribed_success": "Successfully subscribed to topic: {topic}", - "unsubscribed_failed": "Failed to unsubscribe from topic: {topic}", - "unsubscribed_success": "Successfully unsubscribed from topic: {topic}", + "nothing_found": "Intet fundet for", + "published_error": "Noget gik galt under udgivelse af besked: {topic} til emne: {message}", + "published_message": "Udgav besked: {message} til emne: {topic}", + "reconnection_error": "Kunne ikke genetablere forbindelse", + "show": "Vis", + "subscribed_failed": "Kunne ikke abonnere på emne: {topic}", + "subscribed_success": "Abonneret på emne: {topic} med succes", + "unsubscribed_failed": "Kunne ikke afmelde abonnement fra emne: {topic}", + "unsubscribed_success": "Afmeldt abonnement fra emne: {topic} med succes", "waiting_send_request": "Venter på at sende anmodning" }, "support": { @@ -866,128 +927,169 @@ "community": "Stil spørgsmål og hjælp andre", "documentation": "Læs mere om Hoppscotch", "forum": "Stil spørgsmål og få svar", - "github": "Follow us on Github", - "shortcuts": "Gennemse appen hurtigere", - "team": "Kom i kontakt med teamet", + "github": "Følg os på Github", + "shortcuts": "Naviger i appen hurtigere", "title": "Support", "twitter": "Følg os på Twitter" }, "tab": { - "authorization": "Bemyndigelse", - "body": "Legeme", - "close": "Close Tab", - "close_others": "Close other Tabs", + "authorization": "Autorisation", + "body": "Meddelelse", + "close": "Luk fane", + "close_others": "Luk andre faner", "collections": "Samlinger", "documentation": "Dokumentation", - "duplicate": "Duplicate Tab", - "environments": "Environments", - "headers": "Overskrifter", - "history": "Historie", + "duplicate": "Duplikér fane", + "environments": "Miljøer", + "headers": "Headers", + "history": "Historik", "mqtt": "MQTT", "parameters": "Parametre", - "pre_request_script": "Script på forhånd", + "pre_request_script": "Pre-request script", "queries": "Forespørgsler", "query": "Forespørgsel", - "schema": "Schema", - "shared_requests": "Shared Requests", + "schema": "Skema", + "shared_requests": "Delte anmodninger", + "codegen": "Generér kode", + "code_snippet": "Kodesnippet", + "share_tab_request": "Del fanens anmodning", "socketio": "Socket.IO", "sse": "SSE", - "tests": "Test", + "tests": "Tests", "types": "Typer", "variables": "Variabler", "websocket": "WebSocket" }, "team": { - "already_member": "You are already a member of this team. Contact your team owner.", - "create_new": "Opret nyt team", - "deleted": "Team slettet", - "edit": "Rediger team", + "already_member": "Du er allerede medlem af dette arbejdsområde. Kontakt din arbejdsområdeejer.", + "create_new": "Opret nyt arbejdsområde", + "deleted": "Arbejdsområde slettet", + "edit": "Redigér arbejdsområde", "email": "E-mail", - "email_do_not_match": "Email doesn't match with your account details. Contact your team owner.", - "exit": "Afslut Team", - "exit_disabled": "Kun ejeren kan ikke forlade teamet", - "failed_invites": "Failed invites", - "invalid_coll_id": "Invalid collection ID", - "invalid_email_format": "E -mailformatet er ugyldigt", - "invalid_id": "Invalid team ID. Contact your team owner.", - "invalid_invite_link": "Invalid invite link", - "invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.", - "invalid_member_permission": "Giv venligst en gyldig tilladelse til teammedlemmet", - "invite": "Invite", - "invite_more": "Invite more", - "invite_tooltip": "Invite people to this workspace", - "invited_to_team": "{owner} invited you to join {team}", - "join": "Invitation accepted", - "join_beta": "Deltag i betaprogrammet for at få adgang til teams.", - "join_team": "Join {team}", - "joined_team": "You have joined {team}", - "joined_team_description": "You are now a member of this team", - "left": "Du forlod holdet", - "login_to_continue": "Login to continue", - "login_to_continue_description": "You need to be logged in to join a team.", - "logout_and_try_again": "Logout and sign in with another account", - "member_has_invite": "This email ID already has an invite. Contact your team owner.", - "member_not_found": "Member not found. Contact your team owner.", + "email_do_not_match": "E-mail matcher ikke dine kontooplysninger. Kontakt din arbejdsområdeejer.", + "exit": "Forlad arbejdsområde", + "exit_disabled": "Kun ejer kan ikke forlade arbejdsområdet", + "failed_invites": "Mislykkede invitationer", + "invalid_coll_id": "Ugyldigt samlings-ID", + "invalid_email_format": "E-mailformat er ugyldigt", + "invalid_id": "Ugyldigt arbejdsområde-ID. Kontakt din arbejdsområdeejer.", + "invalid_invite_link": "Ugyldigt invitationslink", + "invalid_invite_link_description": "Linket, du fulgte, er ugyldigt. Kontakt din arbejdsområdeejer.", + "invalid_member_permission": "Angiv venligst en gyldig tilladelse til arbejdsområdemedlemmet", + "invite": "Invitér", + "invite_more": "Invitér flere", + "invite_tooltip": "Invitér folk til dette arbejdsområde", + "invited_to_team": "{owner} inviterede dig til at tilslutte dig {workspace}", + "join": "Invitation accepteret", + "join_team": "Tilslut {workspace}", + "joined_team": "Du har tilsluttet dig {workspace}", + "joined_team_description": "Du er nu medlem af dette arbejdsområde", + "left": "Du forlod arbejdsområdet", + "login_to_continue": "Log ind for at fortsætte", + "login_to_continue_description": "Du skal være logget ind for at tilslutte dig et arbejdsområde.", + "logout_and_try_again": "Log ud og log ind med en anden konto", + "member_has_invite": "Denne e-mail-ID har allerede en invitation. Kontakt din arbejdsområdeejer.", + "member_not_found": "Medlem ikke fundet. Kontakt din arbejdsområdeejer.", "member_removed": "Bruger fjernet", "member_role_updated": "Brugerroller opdateret", "members": "Medlemmer", - "more_members": "+{count} more", - "name_length_insufficient": "Holdnavnet bør mindst være 6 tegn langt", - "name_updated": "Team name updated", - "new": "Nyt team", - "new_created": "Nyt team oprettet", - "new_name": "Mit nye hold", - "no_access": "Du har ikke redigeringsadgang til disse samlinger", - "no_invite_found": "Invitation not found. Contact your team owner.", - "no_request_found": "Request not found.", - "not_found": "Team not found. Contact your team owner.", - "not_valid_viewer": "You are not a valid viewer. Contact your team owner.", - "parent_coll_move": "Cannot move collection to a child collection", - "pending_invites": "Pending invites", + "more_members": "+{count} flere", + "name_length_insufficient": "Arbejdsområdenavn skal være mindst 6 tegn langt", + "name_updated": "Arbejdsområdenavn opdateret", + "new": "Nyt arbejdsområde", + "new_created": "Nyt arbejdsområde oprettet", + "new_name": "Mit nye arbejdsområde", + "no_access": "Du har ikke redigeringsadgang til dette arbejdsområde", + "no_invite_found": "Invitation ikke fundet. Kontakt din arbejdsområdeejer.", + "no_request_found": "Anmodning ikke fundet.", + "not_found": "Arbejdsområde ikke fundet. Kontakt din arbejdsområdeejer.", + "not_valid_viewer": "Du er ikke en gyldig betragter. Kontakt din arbejdsområdeejer.", + "parent_coll_move": "Kan ikke flytte samling til en undersamling", + "pending_invites": "Ventende invitationer", "permissions": "Tilladelser", - "same_target_destination": "Same target and destination", - "saved": "Hold reddet", - "select_a_team": "Select a team", - "success_invites": "Success invites", - "title": "Hold", - "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." + "same_target_destination": "Samme mål og destination", + "saved": "Arbejdsområde gemt", + "select_a_team": "Vælg et arbejdsområde", + "success_invites": "Vellykkede invitationer", + "title": "Arbejdsområder", + "we_sent_invite_link": "Vi sendte et invitationslink til alle inviterede!", + "invite_sent_smtp_disabled": "Invitationslinks genereret", + "we_sent_invite_link_description": "Bed alle inviterede om at tjekke deres indbakke. Klik på linket for at tilslutte sig arbejdsområdet.", + "invite_sent_smtp_disabled_description": "Afsendelse af invitations-e-mails er deaktiveret for denne instans af Hoppscotch. Brug venligst knappen Kopiér link til at kopiere og dele invitationslinket manuelt.", + "copy_invite_link": "Kopiér invitationslink", + "search_title": "Teamanmodninger" }, "team_environment": { - "deleted": "Environment Deleted", - "duplicate": "Environment Duplicated", - "not_found": "Environment not found." + "deleted": "Miljø slettet", + "duplicate": "Miljø duplikeret", + "not_found": "Miljø ikke fundet." }, "test": { - "failed": "test failed", - "javascript_code": "JavaScript -kode", + "failed": "test mislykkedes", + "javascript_code": "JavaScript-kode", "learn": "Læs dokumentation", - "passed": "test passed", - "report": "Test rapport", - "results": "Test resultater", - "script": "Manuskript", - "snippets": "Uddrag" + "passed": "test bestået", + "report": "Testrapport", + "results": "Testresultater", + "script": "Script", + "snippets": "Snippets" }, "websocket": { - "communication": "Meddelelse", + "communication": "Kommunikation", "log": "Log", "message": "Besked", "protocols": "Protokoller", "url": "URL" }, "workspace": { - "change": "Change workspace", - "personal": "My Workspace", - "team": "Team Workspace", - "title": "Workspaces" - }, - "shortcodes": { - "actions": "Actions", - "created_on": "Created on", - "deleted": "Shortcode deleted", - "method": "Method", - "not_found": "Shortcode not found", - "short_code": "Short code", - "url": "URL" + "change": "Skift arbejdsområde", + "personal": "Personligt arbejdsområde", + "other_workspaces": "Mine arbejdsområder", + "team": "Arbejdsområde", + "title": "Arbejdsområder" + }, + "site_protection": { + "login_to_continue": "Log ind for at fortsætte", + "login_to_continue_description": "Du skal være logget ind for at få adgang til denne Hoppscotch Enterprise-instans.", + "error_fetching_site_protection_status": "Noget gik galt under hentning af sitebeskyttelsesstatus" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personlige adgangstokens", + "section_description": "Personlige adgangstokens hjælper dig i øjeblikket med at forbinde CLI'en til din Hoppscotch-konto", + "last_used_on": "Sidst brugt den", + "expires_on": "Udløber den", + "no_expiration": "Ingen udløbsdato", + "expired": "Udløbet", + "copy_token_warning": "Sørg for at kopiere dit personlige adgangstoken nu. Du vil ikke kunne se det igen!", + "token_purpose": "Hvad er dette token til?", + "expiration_label": "Udløb", + "scope_label": "Omfang", + "workspace_read_only_access": "Skrivebeskyttet adgang til arbejdsområdedata.", + "personal_workspace_access_limitation": "Personlige adgangstokens kan ikke få adgang til dit personlige arbejdsområde.", + "generate_token": "Generér token", + "invalid_label": "Angiv venligst en etiket for tokenet", + "no_expiration_verbose": "Dette token vil aldrig udløbe!", + "token_expires_on": "Dette token udløber den", + "generate_new_token": "Generér nyt token", + "generate_modal_title": "Nyt personligt adgangstoken", + "deletion_success": "Adgangstokenet {label} er blevet slettet" + }, + "collection_runner": { + "collection_id": "Samlings-ID", + "environment_id": "Miljø-ID", + "cli_collection_id_description": "Dette samlings-ID vil blive brugt af CLI-samlingsløberen for Hoppscotch.", + "cli_environment_id_description": "Dette miljø-ID vil blive brugt af CLI-samlingsløberen for Hoppscotch.", + "include_active_environment": "Inkludér aktivt miljø:", + "cli": "CLI", + "ui": "Løber (kommer snart)", + "cli_command_generation_description_cloud": "Kopiér nedenstående kommando og kør den fra CLI'en. Angiv venligst et personligt adgangstoken.", + "cli_command_generation_description_sh": "Kopiér nedenstående kommando og kør den fra CLI'en. Angiv venligst et personligt adgangstoken og verificér den genererede SH-instans serveradresse.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Kopiér nedenstående kommando og kør den fra CLI'en. Angiv venligst et personligt adgangstoken og SH-instansens serveradresse.", + "run_collection": "Kør samling" + }, + "ai_experiments": { + "generate_request_name": "Generér anmodningsnavn ved hjælp af AI", + "generate_or_modify_request_body": "Generér eller modificér anmodning meddelelse" } } diff --git a/packages/hoppscotch-common/locales/de.json b/packages/hoppscotch-common/locales/de.json index cd483aed07..0192acebd1 100644 --- a/packages/hoppscotch-common/locales/de.json +++ b/packages/hoppscotch-common/locales/de.json @@ -1,50 +1,59 @@ { "action": { - "add": "Add", - "autoscroll": "Autoscroll", + "add": "Hinzufügen", + "autoscroll": "Automatisch Scrollen", "cancel": "Abbrechen", "choose_file": "Datei auswählen", + "choose_workspace": "Arbeitsbereich auswählen", + "choose_collection": "Sammlung auswählen", + "select_workspace": "Arbeitsbereich auswählen", "clear": "Zurücksetzen", "clear_all": "Alles zurücksetzen", - "clear_history": "Clear all History", - "close": "Close", + "clear_history": "Alle Verlaufsdaten löschen", + "close": "Schließen", "connect": "Verbinden", - "connecting": "Connecting", + "connecting": "Verbinde", "copy": "Kopieren", - "create": "Create", + "create": "Erstellen", "delete": "Löschen", "disconnect": "Trennen", "dismiss": "Verwerfen", "dont_save": "Nicht speichern", "download_file": "Datei herunterladen", - "drag_to_reorder": "Drag to reorder", + "drag_to_reorder": "Ziehen zum Umordnen", "duplicate": "Duplizieren", "edit": "Bearbeiten", - "filter": "Filter", + "filter": "Filtern", "go_back": "Zurück", - "go_forward": "Go forward", - "group_by": "Group by", - "label": "Etikett", + "go_forward": "Weiter", + "group_by": "Gruppiere nach", + "hide_secret": "Geheimnis verbergen", + "label": "Bezeichnung", "learn_more": "Mehr erfahren", + "download_here": "Hier herunterladen", "less": "Weniger", "more": "Mehr", "new": "Neu", "no": "Nein", - "open_workspace": "Open workspace", + "open_workspace": "Arbeitsbereich öffnen", "paste": "Einfügen", "prettify": "Verschönern", - "properties": "Properties", + "properties": "Eigenschaften", + "register": "Registrieren", "remove": "Entfernen", - "rename": "Rename", + "rename": "Umbenennen", "restore": "Wiederherstellen", + "retry": "Wiederholen", "save": "Speichern", - "scroll_to_bottom": "Scroll to bottom", - "scroll_to_top": "Scroll to top", + "save_as_example": "Als Beispiel speichern", + "scroll_to_bottom": "Zum Ende scrollen", + "scroll_to_top": "Zum Anfang scrollen", "search": "Suchen", "send": "Senden", - "share": "Share", - "start": "Start", - "starting": "Starting", + "share": "Teilen", + "show_secret": "Geheimnis anzeigen", + "start": "Starten", + "starting": "Startet", "stop": "Stopp", "to_close": "zum Schließen", "to_navigate": "zum Navigieren", @@ -52,18 +61,41 @@ "turn_off": "Ausschalten", "turn_on": "Einschalten", "undo": "Rückgängig machen", + "verify": "Überprüfen", "yes": "Ja" }, "add": { "new": "Neue hinzufügen", "star": "Stern hinzufügen" }, + "agent": { + "registration_instruction": "Bitte registrieren Sie Hoppscotch Agent mit Ihrem Webclient, um fortzufahren.", + "enter_otp_instruction": "Bitte geben Sie den Verifizierungscode ein, der vom Hoppscotch Agent generiert wurde, und schließen Sie die Registrierung ab.", + "otp_label": "Verifizierungscode", + "processing": "Verarbeite die Anfrage...", + "not_running": "Der Hoppscotch Agent läuft nicht. Bitte starten Sie den Agenten und klicken Sie auf 'Wiederholen'.", + "not_running_title": "Agent nicht erkannt", + "registration_title": "Agent registrierung", + "verify_ssl_certs": "SSL-Zertifikate überprüfen", + "client_certs": "Client-Zertifikate", + "use_http_proxy": "HTTP-Proxy verwenden", + "proxy_capabilities": "Hoppscotch Agent unterstützt HTTP/HTTPS/SOCKS-Proxys sowie NTLM und Basic Auth für Proxys. Geben Sie den Benutzernamen und das Passwort für die Proxy-Authentifizierung in der URL an.", + "add_cert_file": "Zertifikatdatei hinzufügen", + "add_client_cert": "Client-Zertifikat hinzufügen", + "add_key_file": "Schlüssel Datei hinzufügen", + "domain": "Domain", + "cert": "Zertifikat", + "key": "Schlüssel", + "pfx_or_pkcs": "PFX/PKCS12", + "pfx_or_pkcs_file": "PFX/PKCS12-Datei", + "add_pfx_or_pkcs_file": "PFX/PKCS12-Datei hinzufügen" + }, "app": { "chat_with_us": "Chatte mit uns", "contact_us": "Kontaktiere uns", "cookies": "Cookies", "copy": "Kopieren", - "copy_interface_type": "Copy interface type", + "copy_interface_type": "Schnittstellentyp kopieren", "copy_user_id": "Kopiere Nutzer-Authentisierungstoken", "developer_option": "Entwickleroptionen", "developer_option_description": "Entwicklungswerkzeuge, die helfen Hoppscotch weiter zu entwickeln und zu warten.", @@ -86,7 +118,7 @@ "search": "Suche", "share": "Teilen", "shortcuts": "Verknüpfungen", - "social_description": "Follow us on social media to stay updated with the latest news, updates and releases.", + "social_description": "Folge uns in den sozialen Medien für die neuesten Nachrichten und Updates", "social_links": "Social links", "spotlight": "Scheinwerfer", "status": "Status", @@ -95,20 +127,25 @@ "twitter": "Twitter", "type_a_command_search": "Gib einen Befehl ein oder suche…", "we_use_cookies": "Wir verwenden Cookies", + "updated_text": "Hoppscotch wurde auf die Version v{version} aktualisiert 🎉", "whats_new": "Was gibt's Neues?", - "wiki": "Wiki" + "see_whats_new": "Sieh dir an, was neu ist", + "wiki": "Wiki", + "default": "standard: {value}" }, "auth": { "account_exists": "Konto existiert mit unterschiedlichen Zugangsdaten - Melde Dich an, um beide Konten zu verknüpfen", "all_sign_in_options": "Alle Anmeldeoptionen", + "continue_with_auth_provider": "Fortfahren mit {provider}", "continue_with_email": "Mit E-Mail anmelden", "continue_with_github": "Mit GitHub anmelden", + "continue_with_github_enterprise": "Fortfahren mit GitHub Enterprise", "continue_with_google": "Mit Google anmelden", "continue_with_microsoft": "Mit Microsoft anmelden", "continue_with_oidc": "Mit OIDC anmelden", "email": "E-Mail-Adresse", "logged_out": "Abgemeldet", - "login": "Anmeldung", + "login": "Anmelden", "login_success": "Erfolgreich eingeloggt", "login_to_hoppscotch": "Anmelden bei Hoppscotch", "logout": "Ausloggen", @@ -120,58 +157,93 @@ }, "authorization": { "generate_token": "Token generieren", - "graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", + "refresh_token": "Aktualisierungs Token", + "graphql_headers": "Autorisierungsheader werden als Teil des Payloads an connection_init gesendet", "include_in_url": "In URL einbinden", "inherited_from": "Inherited from {auth} from Parent Collection {collection} ", "learn": "Dokumentation", "oauth": { - "redirect_auth_server_returned_error": "Auth Server returned an error state", - "redirect_auth_token_request_failed": "Request to get the auth token failed", - "redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token", - "redirect_invalid_state": "Invalid State value present in the redirect", - "redirect_no_auth_code": "No Authorization Code present in the redirect", - "redirect_no_client_id": "No Client ID defined", - "redirect_no_client_secret": "No Client Secret Defined", - "redirect_no_code_verifier": "No Code Verifier Defined", - "redirect_no_token_endpoint": "No Token Endpoint Defined", - "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", - "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "redirect_auth_server_returned_error": "Der Auth-Server hat einen Fehlerstatus zurückgegeben.", + "redirect_auth_token_request_failed": "Anforderung des Auth-Token ist fehlgeschlagen.", + "redirect_auth_token_request_invalid_response": "Ungültige Antwort vom Token-Endpunkt bei der Anforderung eines Auth-Tokens.", + "redirect_invalid_state": "Ungültiger Statuswert im Redirect vorhanden.", + "redirect_no_auth_code": "Kein Autorisierungscode im Redirect vorhanden.", + "redirect_no_client_id": "Keine Client-ID definiert.", + "redirect_no_client_secret": "Kein Client-Geheimnis definiert.", + "redirect_no_code_verifier": "Kein Code-Verifier definiert.", + "redirect_no_token_endpoint": "Kein Token-Endpunkt definiert.", + "something_went_wrong_on_oauth_redirect": "Beim OAuth-Redirect ist etwas schiefgegangen.", + "something_went_wrong_on_token_generation": "Beim Generieren des Tokens ist etwas schiefgegangen.", + "token_generation_oidc_discovery_failed": "Fehler bei der Token-Generierung: OpenID Connect Discovery fehlgeschlagen.", + "grant_type": "Grant-Typ", + "grant_type_auth_code": "Autorisierungscode", + "token_fetched_successfully": "Token erfolgreich abgerufen.", + "token_fetch_failed": "Abrufen des Tokens fehlgeschlagen.", + "validation_failed": "Validierung fehlgeschlagen, bitte überprüfen Sie die Formularfelder.", + "no_refresh_token_present": "Kein Aktualisierungs-Token vorhanden. Bitte führen Sie den Token-Generierungsprozess erneut aus", + "refresh_token_request_failed": "Aktualisierungs-Token-Anforderung fehlgeschlagen", + "token_refreshed_successfully": "Token erfolgreich aktualisiert", + "label_authorization_endpoint": "Autorisierungsendpunkt", + "label_client_id": "Client-ID", + "label_client_secret": "Client-Geheimnis", + "label_code_challenge": "Code-Challenge", + "label_code_challenge_method": "Code-Challenge-Methode", + "label_code_verifier": "Code-Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token-Endpunkt", + "label_use_pkce": "PKCE verwenden", + "label_implicit": "Implizit", + "label_password": "Passwort", + "label_username": "Benutzername", + "label_auth_code": "Autorisierungscode", + "label_client_credentials": "Client-Anmeldeinformationen" }, "pass_key_by": "Übertragungsart", + "pass_by_query_params_label": "Abfrageparameter", + "pass_by_headers_label": "Headers", "password": "Passwort", - "save_to_inherit": "Please save this request in any collection to inherit the authorization", + "save_to_inherit": "Bitte speichere diese Anfrage in einer Sammlung, um die Autorisierung zu erben", "token": "Token", "type": "Autorisierungsverfahren", - "username": "Nutzername" + "username": "Nutzername", + "aws_signature": { + "access_key": "Zugangs-Schlüssel", + "secret_key": "Geheimer Schlüssel", + "service_name": "Dienstname", + "aws_region": "AWS-Region", + "service_token": "Dienst-Token", + "advance_config": "Erweiterte Konfiguration", + "advance_config_description": "Hoppscotch weist automatisch Standardwerte für bestimmte Felder zu, wenn kein expliziter Wert angegeben wird." + } }, "collection": { "created": "Sammlung erstellt", - "different_parent": "Cannot reorder collection with different parent", + "different_parent": "Sammlung kann nicht neu geordnet werden, weil sie ein anderes übergeordnetes Element hat", "edit": "Sammlung bearbeiten", - "import_or_create": "Import or create a collection", + "import_or_create": "Sammlung erstellen oder importieren", + "import_collection": "Sammlung importieren", "invalid_name": "Bitte gib einen gültigen Namen für die Sammlung an", - "invalid_root_move": "Collection already in the root", - "moved": "Moved Successfully", + "invalid_root_move": "Sammlung bereits im Stammverzeichnis", + "moved": "Erfolgreich verschoben", "my_collections": "Meine Sammlungen", "name": "Meine neue Sammlung", - "name_length_insufficient": "Sammlungsname soll mindestens 3 Zeichen lang sein", + "name_length_insufficient": "Sammlungsname muss mindestens 3 Zeichen lang sein", "new": "Neue Sammlung", - "order_changed": "Collection Order Updated", - "properties": "Collection Properties", - "properties_updated": "Collection Properties Updated", + "order_changed": "Reihenfolge der Sammlung aktualisiert", + "properties": "Eigenschaften der Sammlung", + "properties_updated": "Eigenschaften der Sammlung aktualisiert", "renamed": "Sammlung umbenannt", "request_in_use": "Anfrage wird ausgeführt", "save_as": "Speichern als", - "save_to_collection": "Save to Collection", + "save_to_collection": "In Sammlung speichern", "select": "Wähle eine Sammlung", "select_location": "Ort auswählen", - "select_team": "Wähle ein Team", - "team_collections": "Teamsammlungen" + "details": "Details", + "duplicated": "Sammlung dupliziert" }, "confirm": { - "close_unsaved_tab": "Are you sure you want to close this tab?", - "close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", + "close_unsaved_tab": "Bist du sicher, dass du dieses Tab schließen möchtest?", + "close_unsaved_tabs": "Bist du sicher, dass du alle Tabs schließen möchtest? {count} nicht gespeicherte Tabs gehen verloren.", "exit_team": "Möchtest Du dieses Team wirklich verlassen?", "logout": "Möchtest Du Dich wirklich abmelden?", "remove_collection": "Möchtest Du diese Sammlung wirklich endgültig löschen?", @@ -179,40 +251,44 @@ "remove_folder": "Möchtest Du diesen Ordner wirklich dauerhaft löschen?", "remove_history": "Möchtest Du wirklich den gesamten Verlauf dauerhaft löschen?", "remove_request": "Möchtest Du diese Anfrage wirklich dauerhaft löschen?", - "remove_shared_request": "Are you sure you want to permanently delete this shared request?", + "remove_response": "Möchtest Du diese Antwort wirklich dauerhaft löschen?", + "remove_shared_request": "Möchtest Du diese geteilte Anfrage wirklich dauerhaft löschen?", "remove_team": "Möchtest Du dieses Team wirklich löschen?", "remove_telemetry": "Möchtest Du die Telemetrie wirklich deaktivieren?", "request_change": "Möchtest Du diese Anfrage verwerfen? Ungespeicherte Änderungen gehen verloren.", - "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Möchtest Du diesen Arbeitsbereich wirklich synchronisieren?" + "save_unsaved_tab": "Möchtest du die Änderungen aus diesem Tab speichern?", + "sync": "Möchtest Du diesen Arbeitsbereich wirklich synchronisieren?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { - "add_parameters": "Add to parameters", - "open_request_in_new_tab": "Open request in new tab", - "set_environment_variable": "Set as variable" + "add_parameters": "Zu Parametern hinzufügen", + "open_request_in_new_tab": "Anfrage in neuem Tab öffnen", + "set_environment_variable": "Als Variable setzen" }, "cookies": { "modal": { - "cookie_expires": "Expires", + "cookie_expires": "Läuft ab", "cookie_name": "Name", - "cookie_path": "Path", - "cookie_string": "Cookie string", - "cookie_value": "Value", - "empty_domain": "Domain is empty", - "empty_domains": "Domain list is empty", - "enter_cookie_string": "Enter cookie string", - "interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.", - "managed_tab": "Managed", - "new_domain_name": "New domain name", - "no_cookies_in_domain": "No cookies set for this domain", + "cookie_path": "Pfad", + "cookie_string": "Cookie-String", + "cookie_value": "Wert", + "empty_domain": "Domain ist leer", + "empty_domains": "Domainliste ist leer", + "enter_cookie_string": "Cookie-String eingeben", + "interceptor_no_support": "Der derzeit ausgewählte Interceptor unterstützt keine Cookies. Wählen Sie einen anderen Interceptor und versuchen Sie es erneut.", + "managed_tab": "Verwaltet", + "new_domain_name": "Neuer Domainname", + "no_cookies_in_domain": "Es sind keine Cookies für diese Domain gesetzt", "raw_tab": "Raw", - "set": "Set a cookie" + "set": "Ein Cookie setzen" } }, "count": { "header": "Header {count}", "message": "Nachricht {count}", "parameter": "Parameter {count}", + "key": "Schlüssel {count}", + "description": "Beschreibung {count}", "protocol": "Protokoll {count}", "value": "Wert {count}", "variable": "Variable {count}" @@ -235,17 +311,20 @@ "invites": "Einladungsliste ist leer", "members": "Team ist leer", "parameters": "Diese Anfrage hat keine Parameter", - "pending_invites": "Es gibt keine offenen Einladungen für dieses Team", + "pending_invites": "Es gibt keine ausstehenden Einladungen für dieses Team", "profile": "Einloggen um das Profil anzusehen", "protocols": "Protokolle sind leer", + "request_variables": "Diese Anfrage hat keine Anfragevariablen", "schema": "Verbinden mit einem GraphQL-Endpunkt", - "shared_requests": "Shared requests are empty", - "shared_requests_logout": "Login to view your shared requests or create a new one", - "subscription": "Subscriptions are empty", + "secret_environments": "Geheimnisse werden nicht mit dem Hoppscotch Server synchronisiert", + "shared_requests": "Keine geteilten Abfragen", + "shared_requests_logout": "Melde dich an, um geteilte Anfragen zu sehen oder eine neue zu erstellen", + "subscription": "Keine Abonnements", "team_name": "Teamname leer", - "teams": "Teams sind leer", + "teams": "Keine Teams", "tests": "Es gibt keine Tests für diese Anfrage", - "shortcodes": "Shortcodes are empty" + "access_tokens": "Zugriffs-Token sind leer", + "response": "Keine Antwort erhalten" }, "environment": { "add_to_global": "Zur Globalen Umgebung hinzufügen", @@ -253,43 +332,50 @@ "create_new": "Neue Umgebung erstellen", "created": "Umgebung erzeugt", "deleted": "Umgebung löschen", - "duplicated": "Environment duplicated", + "duplicated": "Umgebung dupliziert", "edit": "Umgebung bearbeiten", - "empty_variables": "No variables", + "empty_variables": "Keine Variablen", "global": "Global", - "global_variables": "Global variables", - "import_or_create": "Import or create a environment", + "global_variables": "Globale Variablen", + "import_or_create": "Importiere oder erstelle eine Umgebung", "invalid_name": "Bitte gib einen gültigen Namen für die Umgebung an", - "list": "Environment variables", - "my_environments": "My Environments", + "list": "Umgebungsvariablen", + "my_environments": "Meine Umgebungen", "name": "Name", "nested_overflow": "Verschachtelte Umgebungsvariablen sind limitert auf 10 Unterebenen", "new": "Neue Umgebung", - "no_active_environment": "No active environment", + "no_active_environment": "Keine Umgebung ausgewählt", "no_environment": "Keine Umgebung", "no_environment_description": "Es wurden keine Umgebungen ausgewählt. Wähle aus, was mit den untenstehenden Variablen geschehen soll.", - "quick_peek": "Environment Quick Peek", - "replace_with_variable": "Replace with variable", + "quick_peek": "Kurzinfo der Umgebung", + "replace_with_variable": "Mit Variable ersetzen", "scope": "Scope", + "secrets": "Geheimnis", + "secret_value": "Geheimwert", "select": "Umgebung auswählen", - "set": "Set environment", - "set_as_environment": "Set as environment", - "team_environments": "Team Environments", + "set": "Umgebung setzen", + "set_as_environment": "Als Umgebung setzen", + "short_name": "Umgebung muss mindestens 3 Zeichen haben", + "team_environments": "Teamumgebungen", "title": "Umgebungen", "updated": "Umgebung aktualisiert", - "value": "Value", + "value": "Wert", "variable": "Variable", - "variable_list": "Variablenliste" + "variables": "Variables", + "variable_list": "Variablenliste", + "properties": "Umgebungsparameter", + "details": "Details" }, "error": { - "authproviders_load_error": "Unable to load auth providers", + "authproviders_load_error": "Auth-Provider können nicht geladen werden", "browser_support_sse": "Dieser Browser scheint keine Unterstützung für die vom Server gesendete Ereignisse zu haben.", "check_console_details": "Einzelheiten findest Du in der Browser-Konsole.", - "check_how_to_add_origin": "Check how you can add an origin", + "check_how_to_add_origin": "Erfahre, wie du eine Quelle hinzufügen kannst", "curl_invalid_format": "cURL ist nicht richtig formatiert", - "danger_zone": "Danger zone", - "delete_account": "Your account is currently an owner in these teams:", - "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "danger_zone": "Gefahrenbereich", + "delete_account": "Dein Konto ist derzeit Besitzer dieser Teams:", + "delete_account_description": "Du musst dich entweder selbst entfernen, den Besitz übertragen oder diese Teams löschen, bevor du dein Konto löschen kannst.", + "empty_profile_name": "Profilname darf nicht leer sein", "empty_req_name": "Leerer Anfragename", "f12_details": "(F12 für Details)", "gql_prettify_invalid_query": "Eine ungültige Abfrage konnte nicht verschönert werden. Fehler in der Abfragesyntax beheben und erneut versuchen", @@ -297,33 +383,49 @@ "incorrect_email": "Falsche E-Mail Adresse", "invalid_link": "Falscher Link", "invalid_link_description": "Der verwendete Link ist entweder ungültig oder abgelaufen.", - "json_parsing_failed": "Invalid JSON", + "invalid_embed_link": "The embed does not exist or is invalid.", + "json_parsing_failed": "JSON ungültig", "json_prettify_invalid_body": "Ein ungültiger Text konnte nicht verschönert werden, JSON-Syntaxfehler beheben und erneut versuchen", "network_error": "Netzwerkfehler. Bitte versuche es erneut.", "network_fail": "Anfrage konnte nicht gesendet werden", - "no_collections_to_export": "No collections to export. Please create a collection to get started.", + "no_collections_to_export": "Keine Sammlungen zu exportieren. Bitte erstelle eine Sammlung, um loszulegen.", "no_duration": "Keine Dauer", - "no_environments_to_export": "No environments to export. Please create an environment to get started.", - "no_results_found": "No matches found", - "page_not_found": "This page could not be found", - "please_install_extension": "Please install the extension and add origin to the extension.", - "proxy_error": "Proxy error", + "no_environments_to_export": "Keine Umgebungen zum Exportieren. Bitte erstelle eine Umgebung, um loszulegen.", + "no_results_found": "Keine Ergebnisse gefunden", + "page_not_found": "Diese Seite konnte nicht gefunden werden", + "please_install_extension": "Bitte installiere die Browser-Erweiterung, um Quellen hinzuzufügen.", + "proxy_error": "Proxy fehler", + "same_profile_name": "Der aktualisierte Profilname ist identisch mit dem aktuellen Profilnamen", "script_fail": "Pre-Request-Skripte konnte nicht ausgeführt werden", "something_went_wrong": "Etwas ist schief gelaufen", - "test_script_fail": "Testskripts konnten nicht ausgeführt werden" + "test_script_fail": "Testskripts konnten nicht ausgeführt werden", + "reading_files": "Fehler beim Lesen einer oder mehrerer Dateien.", + "fetching_access_tokens_list": "Beim Abrufen der Liste der Tokens ist etwas schiefgegangen.", + "generate_access_token": "Beim Generieren des Zugriffstokens ist etwas schiefgegangen.", + "delete_access_token": "Beim Löschen des Zugriffstokens ist etwas schiefgegangen." }, "export": { "as_json": "Als JSON exportieren", - "create_secret_gist": "Geheimen Github Gist erstellen", - "failed": "Something went wrong while exporting", - "gist_created": "Gist erstellt", - "require_github": "Melde Dich bei GitHub an, um einen geheimen Gist zu erstellen", - "title": "Export" + "create_secret_gist": "Geheimes Gist erstellen", + "create_secret_gist_tooltip_text": "Als geheimes Gist exportieren", + "failed": "Beim Exportieren ist etwas schiefgegangen", + "secret_gist_success": "Erfolgreich als geheimes Gist exportiert", + "require_github": "Melden Sie sich mit GitHub an, um ein geheimes Gist zu erstellen", + "title": "Exportieren", + "success": "Erfolgreich exportiert" + }, + "filename": { + "cookie_key_value_pairs": "Cookies", + "codegen": "{request_name} - Code", + "graphql_response": "GraphQL-Antwort", + "lens": "{request_name} - Antwort", + "realtime_response": "Echtzeit-Antwort", + "response_interface": "Antwort-Schnittstelle" }, "filter": { - "all": "All", - "none": "None", - "starred": "Starred" + "all": "Alle", + "none": "Keine", + "starred": "Favoriten" }, "folder": { "created": "Ordner erstellt", @@ -334,16 +436,17 @@ "renamed": "Ordner umbenannt" }, "graphql": { - "connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?", - "connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is", - "connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is", + "connection_switch_confirm": "Möchtest du dich mit dem letzten GraphQL-Endpunkt verbinden?", + "connection_switch_new_url": "Wenn du zu einem anderen Tab wechselst, wird die aktive GraphQL-Verbindung getrennt. Die neue Verbindungs-URL lautet", + "connection_switch_url": "Du bist mit einem GraphQL-Endpunkt verbunden, die Verbindungs-URL lautet", "mutations": "Mutationen", "schema": "Schema", "subscriptions": "Abonnements", - "switch_connection": "Switch connection" + "switch_connection": "Verbindung wechseln", + "url_placeholder": "Gib eine GraphQL-Endpunkt-URL ein" }, "graphql_collections": { - "title": "GraphQL Collections" + "title": "GraphQL Sammlungen" }, "group": { "time": "Time", @@ -356,8 +459,8 @@ }, "helpers": { "authorization": "Der Autorisierungsheader wird automatisch generiert, wenn Du die Anfrage sendest.", - "collection_properties_authorization": " This authorization will be set for every request in this collection.", - "collection_properties_header": "This header will be set for every request in this collection.", + "collection_properties_authorization": "Diese Autorisierung wird für jede Anfrage in dieser Sammlung gesetzt.", + "collection_properties_header": "Dieser Header wird für jede Anfrage in dieser Sammlung gesetzt.", "generate_documentation_first": "Zuerst Dokumentation erstellen", "network_fail": "Der API-Endpunkt kann nicht erreicht werden. Überprüfe Deine Netzwerkverbindung und versuche es erneut.", "offline": "Du scheinst offline zu sein. Die Daten in diesem Arbeitsbereich sind möglicherweise nicht aktuell.", @@ -377,59 +480,68 @@ "import": { "collections": "Sammlungen importieren", "curl": "cURL importieren", - "environments_from_gist": "Import From Gist", - "environments_from_gist_description": "Import Hoppscotch Environments From Gist", + "environments_from_gist": "Aus Gist importieren", + "environments_from_gist_description": "Hoppscotch-Umgebungen aus Gist importieren", "failed": "Importieren fehlgeschlagen", - "from_file": "Import from File", - "from_gist": "Von Github Gist importieren", - "from_gist_description": "Von Github Gist URL importieren", - "from_insomnia": "Von Insomnia importieren", + "from_file": "Aus Datei importieren", + "from_gist": "Aus GitHub Gist importieren", + "from_gist_description": "Aus GitHub Gist URL importieren", + "from_insomnia": "Aus Insomnia importieren", "from_insomnia_description": "Insomnia-Sammlung importieren", - "from_json": "Von Hoppscotch importieren", + "from_json": "Aus Hoppscotch importieren", "from_json_description": "Hoppscotch-Sammlung importieren", "from_my_collections": "Aus 'Meine Sammlungen' importieren", "from_my_collections_description": "Meine-Sammlungen-Datei importieren", - "from_openapi": "Von OpenAPI importieren", + "from_all_collections": "Von einem anderen Arbeitsbereich importieren", + "from_all_collections_description": "Importiere jede Sammlung von einem anderen Arbeitsbereich in den aktuellen Arbeitsbereich", + "from_openapi": "Aus OpenAPI importieren", "from_openapi_description": "OpenAPI-Spezifikation importieren (YAML/JSON)", - "from_postman": "Von Postman importieren", + "from_postman": "Aus Postman importieren", "from_postman_description": "Postman-Sammlung importieren", - "from_url": "Von URL importieren", + "from_url": "Aus URL importieren", "gist_url": "Gist-URL eingeben", - "gql_collections_from_gist_description": "Import GraphQL Collections From Gist", - "hoppscotch_environment": "Hoppscotch Environment", - "hoppscotch_environment_description": "Import Hoppscotch Environment JSON file", - "import_from_url_invalid_fetch": "Couldn't get data from the url", - "import_from_url_invalid_file_format": "Error while importing collections", - "import_from_url_invalid_type": "Unsupported type. accepted values are 'hoppscotch', 'openapi', 'postman', 'insomnia'", - "import_from_url_success": "Collections Imported", - "insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file", - "json_description": "Hoppscotch Sammlungsdatei (JSON) importieren", - "postman_environment": "Postman Environment", - "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Importieren" + "from_har": "Von HAR importieren", + "from_har_description": "Von HAR-Datei importieren", + "gql_collections_from_gist_description": "GraphQL-Sammlungen aus Gist importieren", + "hoppscotch_environment": "Hoppscotch-Umgebung", + "hoppscotch_environment_description": "Hoppscotch-Umgebung aus JSON-Datei importieren", + "import_from_url_invalid_fetch": "Konnte keine Daten aus der URL abrufen", + "import_from_url_invalid_file_format": "Fehler beim Importieren von Sammlungen", + "import_from_url_invalid_type": "Typ wird nicht unterstützt. Akzeptierte Werte sind: 'hoppscotch', 'openapi', 'postman', 'insomnia'", + "import_from_url_success": "Sammlungen importiert", + "insomnia_environment_description": "Insomnia-Umgebung aus YAML/JSON-Datei importieren", + "json_description": "Hoppscotch-Sammlung aus JSON-Datei importieren", + "postman_environment": "Postman Umgebung", + "postman_environment_description": "Postman-Umgebung aus einer JSON-Datei importieren", + "title": "Importieren", + "file_size_limit_exceeded_warning_multiple_files": "Die gewählten Dateien überschreiten das Limit von 10 MB. Nur die ersten {files} ausgewählten Dateien werden importiert.", + "file_size_limit_exceeded_warning_single_file": "Die aktuell gewählte Datei überschreitet das empfohlene Limit von 10 MB. Bitte wählen Sie eine andere Datei.", + "success": "Erfolgreich importiert" }, "inspections": { - "description": "Inspect possible errors", + "description": "Mögliche Fehler überprüfen", "environment": { - "add_environment": "Add to Environment", - "not_found": "Environment variable “{environment}” not found." + "add_environment": "Zur Umgebung hinzufügen", + "add_environment_value": "Add value", + "empty_value": "Der Umgebungswert ist für die Variable '{variable}' leer", + "not_found": "Umgebungsvariable “{environment}” nicht gefunden." }, "header": { - "cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." + "cookie": "Der Browser erlaubt Hoppscotch nicht, den Cookie-Header zu setzen. Während wir an der Hoppscotch Desktop App arbeiten (kommt bald), verwende bitte stattdessen den Authorization Header." }, "response": { - "401_error": "Please check your authentication credentials.", - "404_error": "Please check your request URL and method type.", - "cors_error": "Please check your Cross-Origin Resource Sharing configuration.", - "default_error": "Please check your request.", - "network_error": "Please check your network connection." + "401_error": "Bitte überprüfe die Authentifizierungsdaten.", + "404_error": "Bitte überprüfe die URL und die HTTP-Anfragemethode.", + "cors_error": "Bitte überprüfe die Konfiguration für Cross-Origin Resource Sharing (CORS)", + "default_error": "Bitte überprüfe deine Anfrage.", + "network_error": "Bitte überprüfe deine Netzwerkverbindung." }, "title": "Inspector", "url": { - "extension_not_installed": "Extension not installed.", - "extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", - "extention_enable_action": "Enable Browser Extension", - "extention_not_enabled": "Extension not enabled." + "extension_not_installed": "Erweiterung nicht installiert.", + "extension_unknown_origin": "Stelle sicher, dass du die Quelle für den API-Endpunkt zur Liste in der Hoppscotch Browsererweiterungen hinzugefügt hast.", + "extention_enable_action": "Browsererweiterung aktivieren", + "extention_not_enabled": "Browsererweiterungen nicht aktiviert." } }, "layout": { @@ -440,25 +552,27 @@ "row": "Horizontales Layout" }, "modal": { - "close_unsaved_tab": "You have unsaved changes", + "close_unsaved_tab": "Du hast ungespeicherte Änderungen", "collections": "Sammlungen", "confirm": "Aktion bestätigen", - "customize_request": "Customize Request", + "customize_request": "Anfrage anpassen", "edit_request": "Anfrage bearbeiten", - "import_export": "Import / Export", - "share_request": "Share Request" + "edit_response": "Antwort bearbeiten", + "import_export": "Importieren / Exportieren", + "response_name": "Antwort Name", + "share_request": "Anfrage teilen" }, "mqtt": { - "already_subscribed": "You are already subscribed to this topic.", - "clean_session": "Clean Session", - "clear_input": "Clear input", - "clear_input_on_send": "Clear input on send", + "already_subscribed": "Du hast dieses Topic bereits abonniert.", + "clean_session": "Sitzung bereinigen", + "clear_input": "Eingaben löschen", + "clear_input_on_send": "Eingaben beim Senden löschen", "client_id": "Client ID", - "color": "Pick a color", + "color": "Wähle eine Farbe", "communication": "Kommunikation", - "connection_config": "Connection Config", - "connection_not_authorized": "This MQTT connection does not use any authentication.", - "invalid_topic": "Please provide a topic for the subscription", + "connection_config": "Verbindungseinstellungen", + "connection_not_authorized": "Diese MQTT-Verbindung verwendet keine Authentifizierung.", + "invalid_topic": "Bitte gib ein Topic für das Abonnement an", "keep_alive": "Keep Alive", "log": "Protokoll", "lw_message": "Last-Will Message", @@ -466,16 +580,16 @@ "lw_retain": "Last-Will Retain", "lw_topic": "Last-Will Topic", "message": "Nachricht", - "new": "New Subscription", - "not_connected": "Please start a MQTT connection first.", + "new": "Neues Abonnement", + "not_connected": "Bitte stelle zuerst eine MQTT-Verbindung her.", "publish": "Veröffentlichen", "qos": "QoS", "ssl": "SSL", "subscribe": "Abonnieren", - "topic": "Thema", - "topic_name": "Themenname", - "topic_title": "Thema veröffentlichen / abonnieren", - "unsubscribe": "Abbestellen", + "topic": "Topic", + "topic_name": "Topic-Name", + "topic_title": "Topic veröffentlichen / abonnieren", + "unsubscribe": "Deabonnieren", "url": "URL" }, "navigation": { @@ -512,6 +626,7 @@ }, "request": { "added": "Anfrage hinzugefügt", + "add": "Anfrage hinzufügen", "authorization": "Autorisierung", "body": "Anfragekörper", "choose_language": "Sprache wählen", @@ -521,21 +636,21 @@ "structured": "Structured", "text": "Text" }, - "different_collection": "Cannot reorder requests from different collections", - "duplicated": "Request duplicated", + "different_collection": "Anfragen aus verschiedenen Sammlungen können nicht neu geordnet werden", + "duplicated": "Anfrage dupliziert", "duration": "Dauer", "enter_curl": "cURL eingeben", "generate_code": "Code generieren", "generated_code": "Generierter Code", - "go_to_authorization_tab": "Go to Authorization tab", - "go_to_body_tab": "Go to Body tab", + "go_to_authorization_tab": "Zum Tab 'Autorisierung' wechseln", + "go_to_body_tab": "Zum Tab 'Anfragekörper' wechseln", "header_list": "Header-Liste", "invalid_name": "Bitte gib einen Namen für die Anfrage an", "method": "Methode", - "moved": "Request moved", + "moved": "Anfrage verschoben", "name": "Anfragename", "new": "Neue Anfrage", - "order_changed": "Request Order Updated", + "order_changed": "Reihenfolge der Anfragen aktualisiert", "override": "Überschreiben", "override_help": "Setze Content-Type in Headers", "overriden": "Überschrieben", @@ -545,56 +660,67 @@ "payload": "Nutzlast", "query": "Anfrage", "raw_body": "Roher Anfragetext", - "rename": "Rename Request", + "rename": "Anfrage umbenennen", "renamed": "Anfrage umbenannt", + "request_variables": "Anfragevariablen", + "response_name_exists": "Antwortname existiert bereits", "run": "Ausführen", "save": "Speichern", "save_as": "Speichern als", "saved": "Anfrage gespeichert", "share": "Teilen", "share_description": "Teile Hoppscotch mit Deinen Freunden", - "share_request": "Share Request", + "share_request": "Anfrage teilen", "stop": "Stop", "title": "Anfrage", "type": "Anfragetyp", "url": "URL", + "url_placeholder": "Gib eine URL ein oder füge einen cURL-Befehl ein", "variables": "Variablen", - "view_my_links": "View my links", + "view_my_links": "Meine Links anzeigen", "copy_link": "Link kopieren" }, "response": { "audio": "Audio", "body": "Antworttext", - "filter_response_body": "Filter JSON response body (uses JSONPath syntax)", + "duplicated": "Antwort dupliziert", + "duplicate_name_error": "Antwort mit demselben Namen existiert bereits", + "filter_response_body": "JSON-Antwortkörper filtern (verwendet JSONPath-Syntax)", "headers": "Header", "html": "HTML", "image": "Bild", "json": "JSON", "pdf": "PDF", + "please_save_request": "Die Anfrage speichern, um ein Beispiel zu erstellen", "preview_html": "HTML-Vorschau", "raw": "Rohdaten", + "renamed": "Antwort umbenannt", "size": "Größe", "status": "Status", "time": "Zeit", "title": "Antwort", "video": "Video", "waiting_for_connection": "auf Verbindung warten", - "xml": "XML" + "xml": "XML", + "generate_data_schema": "Daten-Schema generieren", + "data_schema": "Daten-Schema", + "saved": "Antwort gespeichert", + "invalid_name": "Bitte geben Sie einen Namen für die Antwort an" }, "settings": { "accent_color": "Akzentfarbe", "account": "Konto", - "account_deleted": "Your account has been deleted", + "account_deleted": "Dein Konto wurde gelöscht", "account_description": "Passe Deine Kontoeinstellungen an.", "account_email_description": "Deine primäre E-Mail-Adresse.", "account_name_description": "Dies ist Dein Anzeigename.", - "additional": "Additional Settings", + "additional": "Weitere Einstellungen", "background": "Hintergrund", "black_mode": "Schwarz", "choose_language": "Sprache wählen", "dark_mode": "Dunkel", - "delete_account": "Delete account", - "delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.", + "delete_account": "Konto löschen", + "delete_account_description": "Wenn du dein Konto löschst, werden alle Daten dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", "expand_navigation": "Menüpunkte vergrößern", "experiments": "Experimente", "experiments_notice": "Dies ist eine Sammlung von Experimenten, an denen wir aktuell arbeiten und die sich als nützlich erweisen könnten, Spaß machen, beides oder keines von beiden. Sie sind nicht endgültig und möglicherweise nicht stabil. Wenn also etwas übermäßig Seltsames passiert, gerate nicht in Panik. Schalte das verdammte Ding einfach aus. Scherz beiseite,", @@ -617,9 +743,10 @@ "proxy_use_toggle": "Verwende die Proxy-Middleware, um Anfragen zu senden", "read_the": "Lies die", "reset_default": "Zurücksetzen", - "short_codes": "Short codes", - "short_codes_description": "Short codes which were created by you.", + "short_codes": "Kurzcodes", + "short_codes_description": "Kurzcodes, die von dir erstellt wurden", "sidebar_on_left": "Seitenleiste links", + "ai_experiments": "KI-Experimente", "sync": "Synchronisieren", "sync_collections": "Sammlungen", "sync_description": "Diese Einstellungen werden mit der Cloud synchronisiert.", @@ -632,31 +759,31 @@ "theme_description": "Passe Dein Anwendungsdesign an.", "use_experimental_url_bar": "Experimentelle URL-Leiste mit Hervorhebung der Umgebung verwenden", "user": "Nutzer", - "verified_email": "Verified email", + "verified_email": "Bestätigte E-Mail-Adresse", "verify_email": "E-Mail-Adresse bestätigen" }, "shared_requests": { - "button": "Button", - "button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.", - "copy_html": "Copy HTML", - "copy_link": "Copy Link", - "copy_markdown": "Copy Markdown", - "creating_widget": "Creating widget", - "customize": "Customize", - "deleted": "Shared request deleted", - "description": "Select a widget, you can change and customize this later", + "button": "Schaltfläche", + "button_info": "Erstelle einen 'Run in Hoppscotch'-Schaltfläche für deine Website, deinen Blog oder eine README.", + "copy_html": "HTML kopieren", + "copy_link": "Link kopieren", + "copy_markdown": "Markdown kopieren", + "creating_widget": "Widget erstellen", + "customize": "Anpassen", + "deleted": "Geteilte Anfrage gelöscht", + "description": "Wähle ein Widget aus. Du kannst es später noch ändern und anpassen.", "embed": "Embed", - "embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.", + "embed_info": "Füge deiner Website, deinem Blog oder deiner Dokumentation einen kleinen 'Hoppscotch API Playground' hinzu.", "link": "Link", - "link_info": "Create a shareable link to share with anyone on the internet with view access.", - "modified": "Shared request modified", - "not_found": "Shared request not found", - "open_new_tab": "Open in new tab", - "preview": "Preview", - "run_in_hoppscotch": "Run in Hoppscotch", + "link_info": "Erstelle einen Link, den du mit jedem im Internet teilen kannst, der Berechtigungen zur Ansicht bekommt.", + "modified": "Geteilte Anfrage geändert", + "not_found": "Geteilte Anfrage nicht gefunden", + "open_new_tab": "In neuem Tab öffnen", + "preview": "Vorschau", + "run_in_hoppscotch": "In Hoppscotch ausführen", "theme": { - "dark": "Dark", - "light": "Light", + "dark": "Dunkel", + "light": "Hell", "system": "System", "title": "Theme" } @@ -703,20 +830,19 @@ "save_request": "Save Request", "save_to_collections": "In Sammlungen speichern", "send_request": "Anfrage senden", - "share_request": "Share Request", - "show_code": "Generate code snippet", - "title": "Anfrage", - "copy_request_link": "Anfragelink kopieren" + "share_request": "Anfrage teilen", + "show_code": "Code-Schnipsel generieren", + "title": "Anfrage" }, "response": { - "copy": "Copy response to clipboard", - "download": "Download response as file", - "title": "Response" + "copy": "Antwort in die Zwischenablage kopieren", + "download": "Antwort als Datei herunterladen", + "title": "Antwort" }, "theme": { "black": "Auf Schwarzes Design wechseln", - "dark": "Auf Dunkles Design wechseln", - "light": "Auf Helles Design wechseln", + "dark": "Auf dunkles Design wechseln", + "light": "Auf helles Design wechseln", "system": "Auf Systemdesign wechseln", "title": "Theme" } @@ -729,89 +855,96 @@ }, "socketio": { "communication": "Kommunikation", - "connection_not_authorized": "This SocketIO connection does not use any authentication.", + "connection_not_authorized": "Diese SocketIO-Verbindung verwendet keine Authentifizierung.", "event_name": "Ereignissname", "events": "Ereigniss", "log": "Protokoll", "url": "URL" }, "spotlight": { - "change_language": "Change Language", + "change_language": "Sprache ändern", "environments": { - "delete": "Delete current environment", - "duplicate": "Duplicate current environment", - "duplicate_global": "Duplicate global environment", - "edit": "Edit current environment", - "edit_global": "Edit global environment", - "new": "Create new environment", - "new_variable": "Create a new environment variable", - "title": "Environments" + "delete": "Aktuelle Umgebung löschen", + "duplicate": "Aktuelle Umgebung duplizieren", + "duplicate_global": "Globale Umgebung duplizieren", + "edit": "Aktuelle Umgebung bearbeiten", + "edit_global": "Globale Umgebung bearbeiten", + "new": "Neue Umgebung erstellen", + "new_variable": "Neue Umgebungsvariable erstellen", + "title": "Umgebungen" }, "general": { - "chat": "Chat with support", - "help_menu": "Help and support", - "open_docs": "Read Documentation", - "open_github": "Open GitHub repository", - "open_keybindings": "Keyboard shortcuts", - "social": "Social", - "title": "General" + "chat": "Chat mit dem Support", + "help_menu": "Hilfe und Support", + "open_docs": "Dokumentation lesen", + "open_github": "GitHub-Repository öffnen", + "open_keybindings": "Tastenkürzel anzeigen", + "social": "Soziale Medien", + "title": "Allgemein" }, "graphql": { - "connect": "Connect to server", - "disconnect": "Disconnect from server" + "connect": "Verbindung zum Server herstellen", + "disconnect": "Verbindung zum Server trennen" }, "miscellaneous": { - "invite": "Invite your friends to Hoppscotch", - "title": "Miscellaneous" + "invite": "Lade deine Freunde zu Hoppscotch ein", + "title": "Verschiedenes" }, "request": { - "save_as_new": "Save as new request", - "select_method": "Select method", - "switch_to": "Switch to", - "tab_authorization": "Authorization tab", - "tab_body": "Body tab", - "tab_headers": "Headers tab", - "tab_parameters": "Parameters tab", - "tab_pre_request_script": "Pre-request script tab", - "tab_query": "Query tab", - "tab_tests": "Tests tab", - "tab_variables": "Variables tab" + "save_as_new": "Als neue Anfrage speichern", + "select_method": "Methode wählen", + "switch_to": "Wechseln zu", + "tab_authorization": "Tab 'Autorisierung'", + "tab_body": "Tab 'Anfragekörper'", + "tab_headers": "Tab 'Header'", + "tab_parameters": "Tab 'Parameter'", + "tab_pre_request_script": "Tab 'Pre-Request-Skripte'", + "tab_query": "Tab 'Anfrage'", + "tab_tests": "Tab 'Tests'", + "tab_variables": "Tab 'Variablen'" }, "response": { - "copy": "Copy response", - "download": "Download response as file", - "title": "Response" + "copy": "Antwort kopieren", + "download": "Antwort als Datei herunterladen", + "title": "Antwort" }, "section": { "interceptor": "Interceptor", - "interface": "Interface", + "interface": "Oberfläche", "theme": "Theme", - "user": "User" + "user": "Benutzer" }, "settings": { - "change_interceptor": "Change Interceptor", - "change_language": "Change Language", + "change_interceptor": "Interceptor ändern", + "change_language": "Sprache ändern", "theme": { - "black": "Black", - "dark": "Dark", - "light": "Light", - "system": "System preference" + "black": "Schwarz", + "dark": "Dunkel", + "light": "Hell", + "system": "System" } }, "tab": { - "close_current": "Close current tab", - "close_others": "Close all other tabs", - "duplicate": "Duplicate current tab", - "new_tab": "Open a new tab", + "close_current": "Aktuelles Tab schließen", + "close_others": "Andere Tabs schließen", + "duplicate": "Tab duplizieren", + "new_tab": "Neues Tab öffnen", "title": "Tabs" }, "workspace": { - "delete": "Delete current team", - "edit": "Edit current team", - "invite": "Invite people to team", - "new": "Create new team", - "switch_to_personal": "Switch to your personal workspace", + "delete": "Aktuelles Team löschen", + "edit": "Aktuelles Team bearbeiten", + "invite": "Leute zum Team einladen", + "new": "Neues Team erstellen", + "switch_to_personal": "Zum eigenen Arbeitsbereich wechseln", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -826,10 +959,10 @@ "connected": "In Verbindung gebracht", "connected_to": "Verbunden mit {name}", "connecting_to": "Verbindung zu {name}...", - "connection_error": "Failed to connect", - "connection_failed": "Connection failed", - "connection_lost": "Connection lost", - "copied_interface_to_clipboard": "Copied {language} interface type to clipboard", + "connection_error": "Verbindung nicht möglich", + "connection_failed": "Verbindungsaufbau fehlgeschlagen", + "connection_lost": "Verbindung unterbrochen", + "copied_interface_to_clipboard": "Den {language} Schnittstellentyp in die Zwischenablage kopiert", "copied_to_clipboard": "In die Zwischenablage kopiert", "deleted": "Gelöscht", "deprecated": "VERALTET", @@ -837,28 +970,30 @@ "disconnected": "Getrennt", "disconnected_from": "Verbindung zu {name} getrennt", "docs_generated": "Dokumentation erstellt", - "download_failed": "Download failed", + "download_failed": "Download fehlgeschlagen", "download_started": "Download gestartet", "enabled": "Aktiviert", "file_imported": "Datei importiert", "finished_in": "Fertig in {duration} ms", - "hide": "Hide", + "hide": "Ausblenden", "history_deleted": "Verlauf gelöscht", "linewrap": "Zeilen umbrechen", "loading": "Wird geladen...", - "message_received": "Message: {message} arrived on topic: {topic}", - "mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}", + "message_received": "Nachricht: {message} eingegangen auf Topic: {topic}", + "mqtt_subscription_failed": "Beim Abonnieren des Topics '{topic}' ist etwas schiefgelaufen.", "none": "Keiner", "nothing_found": "Nichts gefunden für", - "published_error": "Something went wrong while publishing msg: {topic} to topic: {message}", - "published_message": "Published message: {message} to topic: {topic}", - "reconnection_error": "Failed to reconnect", - "show": "Show", - "subscribed_failed": "Failed to subscribe to topic: {topic}", - "subscribed_success": "Successfully subscribed to topic: {topic}", - "unsubscribed_failed": "Failed to unsubscribe from topic: {topic}", - "unsubscribed_success": "Successfully unsubscribed from topic: {topic}", - "waiting_send_request": "Warten auf Anfrage senden" + "published_error": "Beim Veröffentlichen der Nachricht '{message}' im Topic '{topic}' ist etwas schiefgelaufen.", + "published_message": "Nachricht '{message}' wurde im Topic '{topic}' veröffentlicht.", + "reconnection_error": "Verbindung fehlgeschlagen", + "show": "Anzeigen", + "subscribed_failed": "Topic '{topic}' konnte nicht abonniert werden.", + "subscribed_success": "Topic '{topic}' wurde erfolgreich abonniert.", + "unsubscribed_failed": "Topic '{topic}' konnte nicht deabonniert werden.", + "unsubscribed_success": "Topic '{topic}' wurde erfolgreich deabonniert.", + "waiting_send_request": "Warten auf Anfrage senden", + "loading_workspaces": "Arbeitsbereiche werden geladen", + "loading_collections_in_workspace": "Sammlungen im Arbeitsbereich werden geladen" }, "support": { "changelog": "Lese mehr über die neuesten Versionen", @@ -866,21 +1001,20 @@ "community": "Stelle Fragen und helfe anderen", "documentation": "Lese mehr über Hoppscotch", "forum": "Stelle Fragen und erhalte Antworten", - "github": "Folge uns auf Github", + "github": "Folge uns auf GitHub", "shortcuts": "Hoppscotch schneller bedienen", - "team": "Nehme Kontakt mit dem Team auf", "title": "Hilfe", "twitter": "Folge uns auf Twitter" }, "tab": { "authorization": "Autorisierung", "body": "Anfragekörper", - "close": "Close Tab", - "close_others": "Close other Tabs", + "close": "Tab schließen", + "close_others": "Andere Tabs schließen", "collections": "Sammlungen", "documentation": "Dokumentation", - "duplicate": "Duplicate Tab", - "environments": "Environments", + "duplicate": "Tab duplizieren", + "environments": "Umgebungen", "headers": "Header", "history": "Verlauf", "mqtt": "MQTT", @@ -889,7 +1023,10 @@ "queries": "Anfragen", "query": "Anfrage", "schema": "Schema", - "shared_requests": "Shared Requests", + "shared_requests": "Geteilte Anfragen", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Tests", @@ -906,8 +1043,8 @@ "email_do_not_match": "E-Mail-Adresse stimmt nicht mit Deinen Kontodaten überein, bitte kontaktiere den Teameigentümer.", "exit": "Team verlassen", "exit_disabled": "Eigentümer können das Team nicht verlassen", - "failed_invites": "Failed invites", - "invalid_coll_id": "Invalid collection ID", + "failed_invites": "Gescheiterte Einladungen", + "invalid_coll_id": "Ungültige Sammlungs-ID", "invalid_email_format": "E-Mail-Format ist ungültig", "invalid_id": "Ungültige Team-ID, bitte kontaktiere den Teameigentümer.", "invalid_invite_link": "Ungültiger Einladungslink.", @@ -915,10 +1052,9 @@ "invalid_member_permission": "Bitte erteile dem Teammitglied eine gültige Erlaubnis", "invite": "Einladen", "invite_more": "Mehr einladen", - "invite_tooltip": "Personen zum Arbeitsbereich einladen", + "invite_tooltip": "Personen zum Team einladen", "invited_to_team": "{owner} hat dich zu {team} eingeladen", "join": "Einladung angenommen", - "join_beta": "Nimm am Beta-Programm teil, um auf Teams zuzugreifen.", "join_team": "{team} beitreten", "joined_team": "Du bist {team} beigetreten", "joined_team_description": "Du bist nun ein Mitglied des Teams", @@ -931,7 +1067,7 @@ "member_removed": "Benutzer entfernt", "member_role_updated": "Benutzerrollen aktualisiert", "members": "Mitglieder", - "more_members": "+{count} more", + "more_members": "+{count} weitere", "name_length_insufficient": "Der Teamname sollte mindestens 6 Zeichen lang sein", "name_updated": "Teamname aktualisiert", "new": "Neues Team", @@ -939,24 +1075,28 @@ "new_name": "Mein neues Team", "no_access": "Du hast keinen Bearbeitungszugriff auf diese Sammlungen", "no_invite_found": "Einladung nicht gefunden, bitte kontaktiere den Teameigentümer.", - "no_request_found": "Request not found.", + "no_request_found": "Anfrage nicht gefunden.", "not_found": "Team wurde nicht gefunde, bitte kontaktiere den Teameigentümer.", "not_valid_viewer": "Du hast nicht die richtige Berechtigung als Gast, bitte kontaktiere den Teameigentümer.", - "parent_coll_move": "Cannot move collection to a child collection", - "pending_invites": "Wartende Einladungen", + "parent_coll_move": "Sammlung kann nicht in eine untergeordnete Sammlung verschoben werden", + "pending_invites": "Ausstehende Einladungen", "permissions": "Berechtigungen", - "same_target_destination": "Same target and destination", + "same_target_destination": "Ziel und Zielort sind identisch", "saved": "Team gespeichert", "select_a_team": "Team auswählen", - "success_invites": "Success invites", + "success_invites": "Erfolgreiche Einladungen", "title": "Team", "we_sent_invite_link": "Einladungen wurden an alle E-Mails verschickt!", - "we_sent_invite_link_description": "Bitte alle eingeladenen Personen, ihren Posteingang zu überprüfen. Klicke auf den Link, um dem Team beizutreten." + "invite_sent_smtp_disabled": "Einladungslinks erstellt", + "we_sent_invite_link_description": "Bitte alle eingeladenen Personen, ihren Posteingang zu überprüfen und auf den Link zu klicken, um dem Team beizutreten.", + "invite_sent_smtp_disabled_description": "Das Versenden von Einladungsemails ist für diese Instanz von Hoppscotch deaktiviert. Bitte verwende die Schaltfläche 'Link kopieren', um den Einladungslink manuell zu kopieren und zu teilen.", + "copy_invite_link": "Einladungslink kopieren", + "search_title": "Team-Anfragen" }, "team_environment": { - "deleted": "Environment Deleted", - "duplicate": "Environment Duplicated", - "not_found": "Environment not found." + "deleted": "Umgebung gelöscht", + "duplicate": "Umgebung dupliziert", + "not_found": "Umgebung nicht gefunden." }, "test": { "failed": "Test fehlgeschlagen", @@ -976,18 +1116,64 @@ "url": "URL" }, "workspace": { - "change": "Change workspace", - "personal": "My Workspace", - "team": "Team Workspace", - "title": "Workspaces" - }, - "shortcodes": { - "actions": "Actions", - "created_on": "Created on", - "deleted": "Shortcode deleted", - "method": "Method", - "not_found": "Shortcode not found", - "short_code": "Short code", - "url": "URL" + "change": "Arbeitsbereich wechseln", + "personal": "Mein Arbeitsbereich", + "other_workspaces": "Meine Arbeitsbereiche", + "team": "Team Arbeitsbereich", + "title": "Arbeitsbereiche" + }, + "site_protection": { + "login_to_continue": "Einloggen, um fortzufahren", + "login_to_continue_description": "Du musst eingeloggt sein, um auf diese Hoppscotch Enterprise-Instanz zuzugreifen.", + "error_fetching_site_protection_status": "Beim Abrufen des Status der Seiten­schutz­maßnahmen ist ein Fehler aufgetreten." + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Persönliche Zugangstoken", + "section_description": "Persönliche Zugangstoken helfen dir derzeit, das CLI mit deinem Hoppscotch-Konto zu verbinden", + "last_used_on": "Zuletzt verwendet am", + "expires_on": "Läuft ab am", + "no_expiration": "Kein Ablaufdatum", + "expired": "Abgelaufen", + "copy_token_warning": "Stelle sicher, dass du dein persönliches Zugangs-Token jetzt kopierst. Du wirst es nicht mehr sehen können!", + "token_purpose": "Wofür ist dieses Token?", + "expiration_label": "Ablaufdatum", + "scope_label": "Geltungsbereich", + "workspace_read_only_access": "Lesezugriff auf Arbeitsbereichsdaten.", + "personal_workspace_access_limitation": "Persönliche Zugangstoken können nicht auf deinen persönlichen Arbeitsbereich zugreifen.", + "generate_token": "Token generieren", + "invalid_label": "Bitte gib eine Bezeichnung für das Token an", + "no_expiration_verbose": "Dieses Token wird nie ablaufen!", + "token_expires_on": "Dieses Token verfällt am", + "generate_new_token": "Neues Token generieren", + "generate_modal_title": "Neues persönliches Zugangstoken", + "deletion_success": "Das persönliche Zugriffstoken {label} wurde gelöscht" + }, + "collection_runner": { + "collection_id": "Sammlungs-ID", + "environment_id": "Umgebungs-ID", + "cli_collection_id_description": "Diese Sammlungs-ID wird vom CLI-Sammlungs-Runner für Hoppscotch verwendet.", + "cli_environment_id_description": "Diese Umgebungs-ID wird vom CLI-Sammlungs-Runner für Hoppscotch verwendet.", + "include_active_environment": "Aktive Umgebung einbeziehen:", + "cli": "CLI", + "ui": "Runner (kommt bald)", + "cli_command_generation_description_cloud": "Kopiere den folgenden Befehl und führe ihn im CLI aus. Bitte gib ein persönliches Zugriffstoken an.", + "cli_command_generation_description_sh": "Kopiere den folgenden Befehl und führe ihn im CLI aus. Bitte gib ein persönliches Zugriffstoken an und überprüfe die generierte SH-Instanz-Server-URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Kopiere den folgenden Befehl und führe ihn im CLI aus. Bitte gib ein persönliches Zugriffstoken und die SH-Instanz-Server-URL an.", + "run_collection": "Sammlung ausführen" + }, + "ai_experiments": { + "generate_request_name": "Anfragenname mit KI generieren", + "generate_or_modify_request_body": "Anfragebody generieren oder ändern", + "modify_with_ai": "Mit KI ändern", + "generate": "Generieren", + "generate_or_modify_request_body_input_placeholder": "Geben Sie Ihren Prompt ein, um den Anfragebody zu ändern", + "accept_change": "Änderung akzeptieren", + "feedback_success": "Feedback erfolgreich eingereicht", + "feedback_failure": "Feedback konnte nicht eingereicht werden", + "feedback_thank_you": "Vielen Dank für Ihr Feedback!", + "feedback_cta_text_long": "Bewerten Sie die Generierung, hilft uns, uns zu verbessern", + "feedback_cta_request_name": "Hat Ihnen der generierte Name gefallen?", + "modify_request_body_error": "Fehler beim Ändern des Anfragebodys" } } diff --git a/packages/hoppscotch-common/locales/el.json b/packages/hoppscotch-common/locales/el.json index 7771e966eb..3b9001ac25 100644 --- a/packages/hoppscotch-common/locales/el.json +++ b/packages/hoppscotch-common/locales/el.json @@ -24,8 +24,10 @@ "go_back": "Πήγαινε πίσω", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Επιγραφή", "learn_more": "Μάθε περισσότερα", + "download_here": "Download here", "less": "Less", "more": "Περισσότερο", "new": "Νέος", @@ -43,6 +45,7 @@ "search": "Αναζήτηση", "send": "Στείλετε", "share": "Share", + "show_secret": "Show secret", "start": "Αρχή", "starting": "Starting", "stop": "Να σταματήσει", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Πληκτρολογήστε μια εντολή ή αναζήτηση…", "we_use_cookies": "Χρησιμοποιούμε cookies", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Τι νέα?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Ο λογαριασμός υπάρχει με διαφορετικά διαπιστευτήρια - Συνδεθείτε για να συνδέσετε και τους δύο λογαριασμούς", "all_sign_in_options": "Όλες οι επιλογές σύνδεσης", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Συνεχίστε με το Email", "continue_with_github": "Συνεχίστε με το GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Συνεχίστε με την Google", "continue_with_microsoft": "Συνεχίστε με την Microsoft", "continue_with_oidc": "Συνεχίστε με το OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Κωδικός πρόσβασης", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Ενδειξη", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Επεξεργασία Συλλογής", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Καταχωρίστε ένα έγκυρο όνομα για τη συλλογή", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Επιλέξτε μια Συλλογή", "select_location": "Επιλέξτε τοποθεσία", + "details": "Details", "select_team": "Επιλέξτε μια ομάδα", "team_collections": "Συλλογές ομάδων" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Είστε βέβαιοι ότι θέλετε να εξαιρεθείτε από την τηλεμετρία;", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Είστε βέβαιοι ότι θέλετε να συγχρονίσετε αυτόν τον χώρο εργασίας;" + "sync": "Είστε βέβαιοι ότι θέλετε να συγχρονίσετε αυτόν τον χώρο εργασίας;", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "Δεν υπάρχουν εκκρεμείς προσκλήσεις για αυτή την ομάδα", "profile": "Συνδεθείτε για προβολή του προφίλ σας", "protocols": "Τα πρωτόκολλα είναι κενά", + "request_variables": "This request does not have any request variables", "schema": "Συνδεθείτε σε ένα τελικό σημείο GraphQL", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Το όνομα της ομάδας είναι κενό", "teams": "Οι ομάδες είναι άδειες", "tests": "Δεν υπάρχουν δοκιμές για αυτό το αίτημα", + "access_tokens": "Access tokens are empty", "shortcodes": "Τα Shortcodes είναι κενά" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Επιλέξτε περιβάλλον", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Αναβάθμιση Περιβάλλοντος", "value": "Value", "variable": "Variable", - "variable_list": "Λίστα μεταβλητών" + "variables": "Variables", + "variable_list": "Λίστα μεταβλητών", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Όνομα κενού αιτήματος", "f12_details": "(F12 για λεπτομέρειες)", "gql_prettify_invalid_query": "Δεν ήταν δυνατή η προεπιλογή ενός μη έγκυρου ερωτήματος, η επίλυση σφαλμάτων σύνταξης ερωτήματος και η δοκιμή ξανά", @@ -297,6 +337,7 @@ "incorrect_email": "Λάθος email", "invalid_link": "Μή έγκυρο link", "invalid_link_description": "Ο σύνδεσμος που επιλέξατε έχει λήξει ή δεν είναι έγκυρος.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Μη έγκυρο JSON", "json_prettify_invalid_body": "Δεν ήταν δυνατή η ομορφιά ενός μη έγκυρου σώματος, η επίλυση σφαλμάτων σύνταξης json και η προσπάθεια ξανά", "network_error": "Από ότι φαίνεται υπάρχει ένα σφάλμα δικτύου. Παρακαλούμε προσπαθήστε ξανά.", @@ -308,17 +349,25 @@ "page_not_found": "Αυτή η σελίδα δεν βρέθηκε", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Δεν ήταν δυνατή η εκτέλεση του σεναρίου πριν από το αίτημα", "something_went_wrong": "Κάτι πήγε στραβά", - "test_script_fail": "Δεν μπορεσε να εκτελεστεί το post-request script" + "test_script_fail": "Δεν μπορεσε να εκτελεστεί το post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Εξαγωγή ως JSON", "create_secret_gist": "Δημιουργήστε μυστική ουσία", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Η ουσία δημιουργήθηκε", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Συνδεθείτε με το GitHub για να δημιουργήσετε μυστική ουσία", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Η ουσία δημιουργήθηκε" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Μεταλλάξεις", "schema": "Σχήμα", "subscriptions": "Συνδρομές", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Εισαγωγή συλλογών αρχείο JSON Hoppscotch Collections", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Εισαγωγή" + "title": "Εισαγωγή", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Σώμα Ακατέργαστου Αιτήματος", "rename": "Rename Request", "renamed": "Το αίτημα μετονομάστηκε", + "request_variables": "Request variables", "run": "Τρέξιμο", "save": "Σώσει", "save_as": "Αποθήκευση ως", @@ -558,6 +614,7 @@ "title": "Αίτηση", "type": "Τύπος αιτήματος", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Μεταβλητές", "view_my_links": "Προβολή των links μου", "copy_link": "Αντιγραφή συνδέσμου" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Κάντε ερωτήσεις και λάβετε απαντήσεις", "github": "Follow us on Github", "shortcuts": "Περιηγηθείτε πιο γρήγορα στην εφαρμογή", - "team": "Επικοινωνήστε με την ομάδα", "title": "Υποστήριξη", - "twitter": "Ακολουθήστε μας στο Twitter" + "twitter": "Ακολουθήστε μας στο Twitter", + "team": "Επικοινωνήστε με την ομάδα" }, "tab": { "authorization": "Εξουσιοδότηση", @@ -890,6 +954,9 @@ "query": "Ερώτηση", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Υποδοχή.IO", "sse": "SSE", "tests": "Δοκιμές", @@ -918,7 +985,6 @@ "invite_tooltip": "Πρόσκληση χρηστών σε αυτό το workspace", "invited_to_team": "{owner} σου έκανε πρόσκληση για να μπεις στην {team}", "join": "Πρόσκληση Αποδεκτή", - "join_beta": "Εγγραφείτε στο πρόγραμμα beta για πρόσβαση σε ομάδες.", "join_team": "Γίνε Μέλος {team}", "joined_team": "Μπήκες στην ομάδα: {team}", "joined_team_description": "Είστε πλέον μέλος αυτής της ομάδας", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Της ομάδας", "we_sent_invite_link": "Στείλαμε έναν σύνδεσμο πρόσκλησης σε όλους!", - "we_sent_invite_link_description": "Ζητήστε από όλους όσους στείλατε πρόσκληση να ελέγξουν τα email τους. Click στον σύνδεσμο για εισαγωγή στην ομάδα." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Ζητήστε από όλους όσους στείλατε πρόσκληση να ελέγξουν τα email τους. Click στον σύνδεσμο για εισαγωγή στην ομάδα.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Εγγραφείτε στο πρόγραμμα beta για πρόσβαση σε ομάδες." }, "team_environment": { "deleted": "Το περιβάλλον διαγράφηκε", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Δράσεις", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json index 08c094a891..3357b76d6e 100644 --- a/packages/hoppscotch-common/locales/en.json +++ b/packages/hoppscotch-common/locales/en.json @@ -4,6 +4,9 @@ "autoscroll": "Autoscroll", "cancel": "Cancel", "choose_file": "Choose a file", + "choose_workspace": "Choose a workspace", + "choose_collection": "Choose a collection", + "select_workspace": "Select a workspace", "clear": "Clear", "clear_all": "Clear all", "clear_history": "Clear all History", @@ -17,6 +20,7 @@ "dismiss": "Dismiss", "dont_save": "Don't save", "download_file": "Download file", + "download_test_report": "Download test report", "drag_to_reorder": "Drag to reorder", "duplicate": "Duplicate", "edit": "Edit", @@ -36,10 +40,13 @@ "paste": "Paste", "prettify": "Prettify", "properties": "Properties", + "register": "Register", "remove": "Remove", "rename": "Rename", "restore": "Restore", + "retry": "Retry", "save": "Save", + "save_as_example": "Save as example", "scroll_to_bottom": "Scroll to bottom", "scroll_to_top": "Scroll to top", "search": "Search", @@ -55,12 +62,38 @@ "turn_off": "Turn off", "turn_on": "Turn on", "undo": "Undo", - "yes": "Yes" + "verify": "Verify", + "yes": "Yes", + "enable": "Enable", + "disable": "Disable" }, "add": { "new": "Add new", "star": "Add star" }, + "agent": { + "registration_instruction": "Please register Hoppscotch Agent with your web client to continue.", + "enter_otp_instruction": "Please enter the verification code generated by Hoppscotch Agent and complete the registration", + "otp_label": "Verification Code", + "processing": "Processing your request...", + "not_running": "The Hoppscotch Agent is not running. Please start the agent and click 'Retry'.", + "not_running_title": "Agent not detected", + "registration_title": "Agent registration", + "verify_ssl_certs": "Verify SSL Certificates", + "ca_certs": "CA Certificates", + "client_certs": "Client Certificates", + "use_http_proxy": "Use HTTP Proxy", + "proxy_capabilities": "Hoppscotch Agent supports HTTP/HTTPS/SOCKS proxies along with NTLM and Basic Auth in those proxies. Include the username and password for the proxy authentication in the URL itself.", + "add_cert_file": "Add Certificate File", + "add_client_cert": "Add Client Certificate", + "add_key_file": "Add Key File", + "domain": "Domain", + "cert": "Certificate", + "key": "Key", + "pfx_or_pkcs": "PFX/PKCS12", + "pfx_or_pkcs_file": "PFX/PKCS12 File", + "add_pfx_or_pkcs_file": "Add PFX/PKCS12 File" + }, "app": { "chat_with_us": "Chat with us", "contact_us": "Contact us", @@ -98,8 +131,11 @@ "twitter": "Twitter", "type_a_command_search": "Type a command or search…", "we_use_cookies": "We use cookies", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "What's new?", - "wiki": "Wiki" + "see_whats_new": "See what’s new", + "wiki": "Wiki", + "default": "default: {value}" }, "auth": { "account_exists": "Account exists with different credential - Login to link both accounts", @@ -125,6 +161,7 @@ }, "authorization": { "generate_token": "Generate Token", + "refresh_token": "Refresh Token", "graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", "include_in_url": "Include in URL", "inherited_from": "Inherited {auth} from parent collection {collection} ", @@ -147,6 +184,9 @@ "token_fetched_successfully": "Token fetched successfully", "token_fetch_failed": "Failed to fetch token", "validation_failed": "Validation Failed, please check the form fields", + "no_refresh_token_present": "No Refresh Token present. Please run the token generation flow again", + "refresh_token_request_failed": "Refresh token request failed", + "token_refreshed_successfully": "Token refreshed successfully", "label_authorization_endpoint": "Authorization Endpoint", "label_client_id": "Client ID", "label_client_secret": "Client Secret", @@ -163,17 +203,42 @@ "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Password", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Token", "type": "Authorization Type", - "username": "Username" + "username": "Username", + "advance_config": "Advanced Configuration", + "advance_config_description": "Hoppscotch automatically assigns default values to certain fields if no explicit value is provided", + "aws_signature": { + "access_key": "Access Key", + "secret_key": "Secret Key", + "service_name": "Service Name", + "aws_region": "AWS Region", + "service_token": "Service Token" + }, + "digest": { + "realm": "Realm", + "nonce": "Nonce", + "algorithm": "Algorithm", + "qop": "qop", + "nonce_count": "Nonce Count", + "client_nonce": "Client Nonce", + "opaque": "Opaque", + "disable_retry": "Disable Retrying Request", + "inspector_warning": "Agent interceptor is recommended when using Digest Authorization." + } }, "collection": { + "title": "Collection", + "run": "Run Collection", "created": "Collection created", "different_parent": "Cannot reorder collection with different parent", "edit": "Edit Collection", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Please provide a name for the collection", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -189,7 +254,9 @@ "save_as": "Save as", "save_to_collection": "Save to Collection", "select": "Select a Collection", - "select_location": "Select location" + "select_location": "Select location", + "details": "Details", + "duplicated": "Collection duplicated" }, "confirm": { "close_unsaved_tab": "Are you sure you want to close this tab?", @@ -201,12 +268,14 @@ "remove_folder": "Are you sure you want to permanently delete this folder?", "remove_history": "Are you sure you want to permanently delete all history?", "remove_request": "Are you sure you want to permanently delete this request?", + "remove_response": "Are you sure you want to permanently delete this response?", "remove_shared_request": "Are you sure you want to permanently delete this shared request?", "remove_team": "Are you sure you want to delete this workspace?", "remove_telemetry": "Are you sure you want to opt-out of Telemetry?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Would you like to restore your workspace from cloud? This will discard your local progress." + "sync": "Would you like to restore your workspace from cloud? This will discard your local progress.", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -235,6 +304,8 @@ "header": "Header {count}", "message": "Message {count}", "parameter": "Parameter {count}", + "key": "Key {count}", + "description": "Description {count}", "protocol": "Protocol {count}", "value": "Value {count}", "variable": "Variable {count}" @@ -268,9 +339,12 @@ "subscription": "Subscriptions are empty", "team_name": "Workspace name empty", "teams": "You don't belong to any workspaces", - "tests": "There are no tests for this request" + "tests": "There are no tests for this request", + "access_tokens": "Access tokens are empty", + "response": "No response received" }, "environment": { + "heading": "Environment", "add_to_global": "Add to Global", "added": "Environment addition", "create_new": "Create new environment", @@ -299,13 +373,16 @@ "select": "Select environment", "set": "Set environment", "set_as_environment": "Set as environment", + "short_name": "Environment needs to have minimum 3 characters", "team_environments": "Workspace Environments", "title": "Environments", "updated": "Environment updated", "value": "Value", "variable": "Variable", "variables": "Variables", - "variable_list": "Variable List" + "variable_list": "Variable List", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -322,6 +399,7 @@ "gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again", "incomplete_config_urls": "Incomplete configuration URLs", "incorrect_email": "Incorrect email", + "invalid_file_type": "Invalid file type for `{filename}`.", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", "invalid_embed_link": "The embed does not exist or is invalid.", @@ -340,7 +418,10 @@ "script_fail": "Could not execute pre-request script", "something_went_wrong": "Something went wrong", "test_script_fail": "Could not execute post-request script", - "reading_files": "Error while reading one or more files." + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Export as JSON", @@ -352,6 +433,14 @@ "title": "Export", "success": "Successfully exported" }, + "filename": { + "cookie_key_value_pairs": "Cookie", + "codegen": "{request_name} - code", + "graphql_response": "GraphQL-Response", + "lens": "{request_name} - response", + "realtime_response": "Realtime-Response", + "response_interface": "Response-Interface" + }, "filter": { "all": "All", "none": "None", @@ -363,16 +452,19 @@ "invalid_name": "Please provide a name for the folder", "name_length_insufficient": "Folder name should be at least 3 characters long", "new": "New Folder", + "run": "Run Folder", "renamed": "Folder renamed" }, "graphql": { "connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?", + "connection_error_http": "Failed to fetch GraphQL Schema due to network error.", "connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is", "connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is", "mutations": "Mutations", "schema": "Schema", "subscriptions": "Subscriptions", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -415,18 +507,28 @@ "from_file": "Import from File", "from_gist": "Import from Gist", "from_gist_description": "Import from Gist URL", + "from_gist_import_summary": "All hoppscotch features are imported.", + "from_hoppscotch_importer_summary": "All hoppscotch features are imported.", "from_insomnia": "Import from Insomnia", "from_insomnia_description": "Import from Insomnia collection", + "from_insomnia_import_summary": "Collections and Requests will be imported.", "from_json": "Import from Hoppscotch", "from_json_description": "Import from Hoppscotch collection file", "from_my_collections": "Import from Personal Collections", "from_my_collections_description": "Import from Personal Collections file", + "from_all_collections": "Import from Another Workspace", + "from_all_collections_description": "Import any collection from Another Workspace to the current workspace", "from_openapi": "Import from OpenAPI", "from_openapi_description": "Import from OpenAPI specification file (YML/JSON)", + "from_openapi_import_summary": "Collections ( will be created from tags ), Requests and response examples will be imported.", "from_postman": "Import from Postman", "from_postman_description": "Import from Postman collection", + "from_postman_import_summary": "Collections, Requests and response examples will be imported.", "from_url": "Import from URL", "gist_url": "Enter Gist URL", + "from_har": "Import from HAR", + "from_har_description": "Import from HAR file", + "from_har_import_summary": "Requests will be imported to a default collection.", "gql_collections_from_gist_description": "Import GraphQL Collections From Gist", "hoppscotch_environment": "Hoppscotch Environment", "hoppscotch_environment_description": "Import Hoppscotch Environment JSON file", @@ -439,9 +541,15 @@ "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", "title": "Import", - "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", - "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", - "success": "Successfully imported" + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of {sizeLimit}MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of {sizeLimit}MB. Please select another file.", + "success": "Successfully imported", + "import_summary_collections_title": "Collections", + "import_summary_requests_title": "Requests", + "import_summary_responses_title": "Responses", + "import_summary_pre_request_scripts_title": "Pre-request scripts", + "import_summary_test_scripts_title": "Test scripts", + "import_summary_not_supported_by_hoppscotch_import": "We do not support importing {featureLabel} from this source right now." }, "inspections": { "description": "Inspect possible errors", @@ -467,6 +575,9 @@ "extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", "extention_enable_action": "Enable Browser Extension", "extention_not_enabled": "Extension not enabled." + }, + "requestBody": { + "active_interceptor_doesnt_support_binary_body": "Sending binary data via the current interceptor is not supported yet." } }, "layout": { @@ -482,7 +593,9 @@ "confirm": "Confirm", "customize_request": "Customize Request", "edit_request": "Edit Request", + "edit_response": "Edit Response", "import_export": "Import / Export", + "response_name": "Response Name", "share_request": "Share Request" }, "mqtt": { @@ -549,6 +662,7 @@ }, "request": { "added": "Request added", + "add": "Add Request", "authorization": "Authorization", "body": "Request Body", "choose_language": "Choose language", @@ -556,8 +670,10 @@ "content_type_titles": { "others": "Others", "structured": "Structured", - "text": "Text" + "text": "Text", + "binary": "Binary" }, + "show_content_type": "Show Content Type", "different_collection": "Cannot reorder requests from different collections", "duplicated": "Request duplicated", "duration": "Duration", @@ -585,6 +701,7 @@ "rename": "Rename Request", "renamed": "Request renamed", "request_variables": "Request variables", + "response_name_exists": "Response name already exists", "run": "Run", "save": "Save", "save_as": "Save as", @@ -596,27 +713,38 @@ "title": "Request", "type": "Request type", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Variables", - "view_my_links": "View my links" + "view_my_links": "View my links", + "generate_name_error": "Failed to generate request name." }, "response": { "audio": "Audio", "body": "Response Body", + "duplicated": "Response duplicated", + "duplicate_name_error": "Same name response already exists", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "headers": "Headers", + "request_headers": "Request Headers", "html": "HTML", "image": "Image", "json": "JSON", "pdf": "PDF", + "please_save_request": "Save the request to create example", "preview_html": "Preview HTML", "raw": "Raw", + "renamed": "Response renamed", "size": "Size", "status": "Status", "time": "Time", "title": "Response", "video": "Video", "waiting_for_connection": "waiting for connection", - "xml": "XML" + "xml": "XML", + "generate_data_schema": "Generate Data Schema", + "data_schema": "Data Schema", + "saved": "Response saved", + "invalid_name": "Please provide a name for the response" }, "settings": { "accent_color": "Accent color", @@ -626,12 +754,16 @@ "account_email_description": "Your primary email address.", "account_name_description": "This is your display name.", "additional": "Additional Settings", + "auto_encode_mode": "Auto", + "auto_encode_mode_tooltip": "Encode the parameters in the request only if some special characters are present", "background": "Background", "black_mode": "Black", "choose_language": "Choose language", "dark_mode": "Dark", "delete_account": "Delete account", "delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.", + "disable_encode_mode_tooltip": "Never encode the parameters in the request", + "enable_encode_mode_tooltip": "Always encode the parameters in the request", "expand_navigation": "Expand navigation", "experiments": "Experiments", "experiments_notice": "This is a collection of experiments we're working on that might turn out to be useful, fun, both, or neither. They're not final and may not be stable, so if something overly weird happens, don't panic. Just turn the dang thing off. Jokes aside, ", @@ -640,11 +772,15 @@ "extensions": "Browser extension", "extensions_use_toggle": "Use the browser extension to send requests (if present)", "follow": "Follow us", + "general": "General", + "general_description": " General settings used in the application", "interceptor": "Interceptor", "interceptor_description": "Middleware between application and APIs.", "language": "Language", "light_mode": "Light", "official_proxy_hosting": "Official Proxy is hosted by Hoppscotch.", + "query_parameters_encoding": "Query Parameters Encoding", + "query_parameters_encoding_description": "Configure encoding for query parameters in requests", "profile": "Profile", "profile_description": "Update your profile details", "profile_email": "Email address", @@ -657,11 +793,18 @@ "short_codes": "Short codes", "short_codes_description": "Short codes which were created by you.", "sidebar_on_left": "Sidebar on left", + "ai_experiments": "AI Experiments", + "ai_request_naming_style": "Request Naming Style", + "ai_request_naming_style_descriptive_with_spaces": "Descriptive With Spaces", + "ai_request_naming_style_camel_case": "Camel Case ( camelCase )", + "ai_request_naming_style_snake_case": "Snake Case ( snake_case )", + "ai_request_naming_style_pascal_case": "Pascal Case ( PascalCase )", "sync": "Synchronise", "sync_collections": "Collections", "sync_description": "These settings are synced to cloud.", "sync_environments": "Environments", "sync_history": "History", + "history_disabled": "History is disabled. Contact your organization admin to enable history", "system_mode": "System", "telemetry": "Telemetry", "telemetry_helps_us": "Telemetry helps us to personalize our operations and deliver the best experience to you.", @@ -848,6 +991,13 @@ "new": "Create new workspace", "switch_to_personal": "Switch to your personal workspace", "title": "Workspaces" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -894,7 +1044,9 @@ "subscribed_success": "Successfully subscribed to topic: {topic}", "unsubscribed_failed": "Failed to unsubscribe from topic: {topic}", "unsubscribed_success": "Successfully unsubscribed from topic: {topic}", - "waiting_send_request": "Waiting to send request" + "waiting_send_request": "Waiting to send request", + "loading_workspaces": "Loading workspaces", + "loading_collections_in_workspace": "Loading collections in workspace" }, "support": { "changelog": "Read more about latest releases", @@ -925,16 +1077,21 @@ "query": "Query", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Tests", "types": "Types", "variables": "Variables", - "websocket": "WebSocket" + "websocket": "WebSocket", + "all_tests": "All Tests", + "passed": "Passed", + "failed": "Failed" }, "team": { - "already_member": "You are already a member of this workspace. Contact your workspace owner.", + "already_member": "This email is associated with an existing user.", "create_new": "Create new workspace", "deleted": "Workspace deleted", "edit": "Edit Workspace", @@ -961,7 +1118,7 @@ "login_to_continue": "Login to continue", "login_to_continue_description": "You need to be logged in to join a workspace.", "logout_and_try_again": "Logout and sign in with another account", - "member_has_invite": "This email ID already has an invite. Contact your workspace owner.", + "member_has_invite": "User already has an invite. Please ask them to check their inbox or revoke and resend the invite.", "member_not_found": "Member not found. Contact your workspace owner.", "member_removed": "User removed", "member_role_updated": "User roles updated", @@ -985,9 +1142,13 @@ "select_a_team": "Select a workspace", "success_invites": "Success invites", "title": "Workspaces", - "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the workspace.", - "search_title": "Team Requests" + "we_sent_invite_link": "Invitations are on the way", + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": " New invitees will receive a link to join the workspace, existing members and pending invitees won't receive a new link.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "user_not_found": "User not found in the instance." }, "team_environment": { "deleted": "Environment Deleted", @@ -995,6 +1156,8 @@ "not_found": "Environment not found." }, "test": { + "requests": "Requests", + "selection": "Selection", "failed": "test failed", "javascript_code": "JavaScript Code", "learn": "Read documentation", @@ -1002,7 +1165,14 @@ "report": "Test Report", "results": "Test Results", "script": "Script", - "snippets": "Snippets" + "snippets": "Snippets", + "run": "Run", + "run_again": "Run again", + "stop": "Stop", + "new_run": "New Run", + "iterations": "Iterations", + "duration": "Duration", + "avg_resp": "Avg. Response Time" }, "websocket": { "communication": "Communication", @@ -1022,5 +1192,74 @@ "login_to_continue": "Login to continue", "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "cli_comming_soon_for_personal_collection": "Collection Runner for personal collections in CLI is coming soon.", + "delay": "Delay", + "negative_delay": "Delay cannot be negative", + "ui": "Runner", + "running_collection": "Running collection", + "run_config": "Run Configuration", + "advanced_settings": "Advanced Settings", + "stop_on_error": "Stop run if an error occurs", + "persist_responses": "Persist responses", + "keep_variable_values": "Keep variable values", + "collection_not_found": "Collection not found. May be deleted or moved.", + "empty_collection": "Collection is empty. Add requests to run.", + "no_response_persist": "The collection runner is presently configured not to persist responses. This setting prevents showing the response data. To modify this behavior, initiate a new run configuration.", + "select_request": "Select a request to see response and test results", + "response_body_lost_rerun": "Response body is lost. Run again the collection to get the response body.", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection", + "no_passed_tests": "No tests passed", + "no_failed_tests": "No tests failed" + }, + "ai_experiments": { + "generate_request_name": "Generate Request Name Using AI", + "generate_or_modify_request_body": "Generate or Modify Request Body", + "modify_with_ai": "Modify with AI", + "generate": "Generate", + "generate_or_modify_request_body_input_placeholder": "Enter your prompt to modify request body", + "accept_change": "Accept Change", + "feedback_success": "Feedback submitted successfully", + "feedback_failure": "Failed to submit feedback", + "feedback_thank_you": "Thank you for your feedback!", + "feedback_cta_text_long": "Rate the generation, helps us to improve", + "feedback_cta_request_name": "Did you like the generated name?", + "modify_request_body_error": "Failed to modify request body", + "generate_or_modify_prerequest_input_placeholder": "Enter a prompt to generate or modify the pre-request script", + "generate_or_modify_test_script_input_placeholder": "Enter a prompt to generate or modify the test script", + "modify_test_script_error": "Failed to modify test script", + "modify_prerequest_error": "Failed to modify pre-request script" } } diff --git a/packages/hoppscotch-common/locales/es.json b/packages/hoppscotch-common/locales/es.json index ce9f7e1502..22278ca26f 100644 --- a/packages/hoppscotch-common/locales/es.json +++ b/packages/hoppscotch-common/locales/es.json @@ -1,17 +1,17 @@ { "action": { - "add": "Add", + "add": "Añadir", "autoscroll": "Desplazamiento automático", "cancel": "Cancelar", "choose_file": "Seleccionar archivo", "clear": "Limpiar", "clear_all": "Limpiar todo", - "clear_history": "Clear all History", + "clear_history": "Borrar todo el historial", "close": "Cerrar", "connect": "Conectar", "connecting": "Conectando", "copy": "Copiar", - "create": "Create", + "create": "Crear", "delete": "Borrar", "disconnect": "Desconectar", "dismiss": "Descartar", @@ -24,16 +24,18 @@ "go_back": "Volver", "go_forward": "Adelante", "group_by": "Agrupar por", + "hide_secret": "Ocultar secreto", "label": "Etiqueta", "learn_more": "Aprender más", + "download_here": "Descargar aquí", "less": "Menos", "more": "Más", "new": "Nuevo", "no": "No", "open_workspace": "Abrir espacio de trabajo", "paste": "Pegar", - "prettify": "Embellecer", - "properties": "Properties", + "prettify": "Formatear", + "properties": "Propiedades", "remove": "Eliminar", "rename": "Rename", "restore": "Restaurar", @@ -42,7 +44,8 @@ "scroll_to_top": "Desplazar hacia arriba", "search": "Buscar", "send": "Enviar", - "share": "Share", + "share": "Compartir", + "show_secret": "Mostrar secreto", "start": "Comenzar", "starting": "Iniciando", "stop": "Detener", @@ -63,7 +66,7 @@ "contact_us": "Contáctanos", "cookies": "Cookies", "copy": "Copiar", - "copy_interface_type": "Copy interface type", + "copy_interface_type": "Copiar tipo de interfaz", "copy_user_id": "Copiar token de autenticación de usuario", "developer_option": "Opciones para desarrolladores", "developer_option_description": "Herramientas para desarrolladores que ayudan en el desarrollo y mantenimiento de Hoppscotch.", @@ -80,14 +83,14 @@ "name": "Hoppscotch", "new_version_found": "Se ha encontrado una nueva versión. Recarga la página para usarla.", "open_in_hoppscotch": "Open in Hoppscotch", - "options": "Options", + "options": "Opciones", "proxy_privacy_policy": "Política de privacidad de proxy", "reload": "Recargar", "search": "Buscar", "share": "Compartir", "shortcuts": "Atajos", - "social_description": "Follow us on social media to stay updated with the latest news, updates and releases.", - "social_links": "Social links", + "social_description": "Síguenos en redes sociales para estar al día de las últimas noticias, actualizaciones y lanzamientos.", + "social_links": "Redes sociales", "spotlight": "Destacar", "status": "Estado", "status_description": "Comprobar el estado del sitio web", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Escribe un comando o buscar algo…", "we_use_cookies": "Usamos cookies", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "¿Qué hay de nuevo?", + "see_whats_new": "Novedades", "wiki": "Wiki" }, "auth": { "account_exists": "La cuenta existe con una credencial diferente - Inicia sesión para vincular ambas cuentas", "all_sign_in_options": "Todas las opciones de inicio de sesión", + "continue_with_auth_provider": "Continuar con {provider}", "continue_with_email": "Continuar con correo electrónico", "continue_with_github": "Continuar con GitHub", + "continue_with_github_enterprise": "Continuar con GitHub Enterprise", "continue_with_google": "Continuar con Google", "continue_with_microsoft": "Continuar con Microsoft", "continue_with_oidc": "Continuar con OIDC", @@ -120,27 +127,48 @@ }, "authorization": { "generate_token": "Generar token", - "graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", + "graphql_headers": "Las cabeceras de autorización se envían como parte de la carga útil de connection_init", "include_in_url": "Incluir en la URL", - "inherited_from": "Inherited from {auth} from Parent Collection {collection} ", + "inherited_from": "Heredado {auth} de colección padre {collection} ", "learn": "Aprender", "oauth": { - "redirect_auth_server_returned_error": "Auth Server returned an error state", - "redirect_auth_token_request_failed": "Request to get the auth token failed", - "redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token", - "redirect_invalid_state": "Invalid State value present in the redirect", - "redirect_no_auth_code": "No Authorization Code present in the redirect", - "redirect_no_client_id": "No Client ID defined", - "redirect_no_client_secret": "No Client Secret Defined", - "redirect_no_code_verifier": "No Code Verifier Defined", - "redirect_no_token_endpoint": "No Token Endpoint Defined", - "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", - "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "redirect_auth_server_returned_error": "El servidor de autenticación ha devuelto un estado de error", + "redirect_auth_token_request_failed": "Fallo en la solicitud de token de autentificación", + "redirect_auth_token_request_invalid_response": "Respuesta no válida del punto final de Token al solicitar un token de autentificación", + "redirect_invalid_state": "Valor de estado no válido presente en la redirección", + "redirect_no_auth_code": "No hay código de autorización en la redirección", + "redirect_no_client_id": "No se ha definido el ID de cliente", + "redirect_no_client_secret": "No se ha definido ningún ID secreto de cliente", + "redirect_no_code_verifier": "No se ha definido ningún verificador de códigos", + "redirect_no_token_endpoint": "No se ha definido ningún punto final de token", + "something_went_wrong_on_oauth_redirect": "Algo ha ido mal durante la redirección OAuth", + "something_went_wrong_on_token_generation": "Algo salió mal en la generación del token", + "token_generation_oidc_discovery_failed": "Fallo en la generación del token: Error en el descubrimiento de OpenID Connect", + "grant_type": "Tipo de autorización", + "grant_type_auth_code": "Código de autorización", + "token_fetched_successfully": "Token recuperado correctamente", + "token_fetch_failed": "Fallo al recuperar el token", + "validation_failed": "Fallo de validación, comprueba los campos del formulario", + "label_authorization_endpoint": "Punto final de autorización", + "label_client_id": "ID de cliente", + "label_client_secret": "Secreto de cliente", + "label_code_challenge": "Código de desafío", + "label_code_challenge_method": "Método código de desafío", + "label_code_verifier": "Verificador de código", + "label_scopes": "Ámbitos", + "label_token_endpoint": "Punto final de token", + "label_use_pkce": "Utilizar PKCE", + "label_implicit": "Implícito", + "label_password": "Contraseña", + "label_username": "Nombre de usuario", + "label_auth_code": "Código de autorización", + "label_client_credentials": "Credenciales del cliente" }, "pass_key_by": "Pasar por", + "pass_by_query_params_label": "Parámetros de consulta", + "pass_by_headers_label": "Cabeceras", "password": "Contraseña", - "save_to_inherit": "Please save this request in any collection to inherit the authorization", + "save_to_inherit": "Por favor, guarda esta solicitud en cualquier colección para heredar la autorización", "token": "Token", "type": "Tipo de autorización", "username": "Nombre de usuario" @@ -149,8 +177,9 @@ "created": "Colección creada", "different_parent": "No se puede reordenar la colección con un padre diferente", "edit": "Editar colección", - "import_or_create": "Import or create a collection", - "invalid_name": "Proporciona un nombre válido para la colección.", + "import_or_create": "Importar o crear una colección", + "import_collection": "Importar colección", + "invalid_name": "El nombre para la colección no es válido", "invalid_root_move": "La colección ya está en la raíz", "moved": "Movido con éxito", "my_collections": "Mis colecciones", @@ -158,20 +187,21 @@ "name_length_insufficient": "El nombre de la colección debe tener al menos 3 caracteres", "new": "Nueva colección", "order_changed": "Orden de colección actualizada", - "properties": "Collection Properties", - "properties_updated": "Collection Properties Updated", + "properties": "Propiedades de la colección", + "properties_updated": "Propiedades de la colección actualizadas", "renamed": "Colección renombrada", "request_in_use": "Solicitud en uso", "save_as": "Guardar como", - "save_to_collection": "Save to Collection", + "save_to_collection": "Guardar en la colección", "select": "Seleccionar colección", "select_location": "Seleccionar ubicación", + "details": "Detalles", "select_team": "Seleccionar equipo", "team_collections": "Colecciones de equipos" }, "confirm": { - "close_unsaved_tab": "Are you sure you want to close this tab?", - "close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", + "close_unsaved_tab": "¿Seguro que quieres cerrar esta pestaña?", + "close_unsaved_tabs": "¿Estás seguro de que quieres cerrar todas las pestañas? {count} pestañas no guardadas se perderán.", "exit_team": "¿Estás seguro de que quieres dejar este equipo?", "logout": "¿Estás seguro de que deseas cerrar la sesión?", "remove_collection": "¿Estás seguro de que deseas eliminar esta colección de forma permanente?", @@ -179,117 +209,127 @@ "remove_folder": "¿Estás seguro de que deseas eliminar esta carpeta de forma permanente?", "remove_history": "¿Estás seguro de que deseas eliminar todo el historial de forma permanente?", "remove_request": "¿Estás seguro de que deseas eliminar esta solicitud de forma permanente?", - "remove_shared_request": "Are you sure you want to permanently delete this shared request?", + "remove_shared_request": "¿Estás seguro de que quieres eliminar definitivamente esta solicitud compartida?", "remove_team": "¿Estás seguro de que deseas eliminar este equipo?", "remove_telemetry": "¿Estás seguro de que deseas darse de baja de la telemetría?", "request_change": "¿Estás seguro de que deseas descartar la solicitud actual, los cambios no guardados se perderán.", "save_unsaved_tab": "¿Deseas guardar los cambios realizados en esta pestaña?", - "sync": "¿Estás seguro de que deseas sincronizar este espacio de trabajo?" + "sync": "¿Estás seguro de que deseas sincronizar este espacio de trabajo?", + "delete_access_token": "¿Estás seguro de que deseas eliminar el token de acceso {tokenLabel}?" }, "context_menu": { - "add_parameters": "Add to parameters", - "open_request_in_new_tab": "Open request in new tab", - "set_environment_variable": "Set as variable" + "add_parameters": "Añadir a parámetros", + "open_request_in_new_tab": "Abrir solicitud en una nueva pestaña", + "set_environment_variable": "Establecer como variable" }, "cookies": { "modal": { - "cookie_expires": "Expires", - "cookie_name": "Name", - "cookie_path": "Path", - "cookie_string": "Cookie string", - "cookie_value": "Value", - "empty_domain": "Domain is empty", - "empty_domains": "Domain list is empty", - "enter_cookie_string": "Enter cookie string", - "interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.", - "managed_tab": "Managed", - "new_domain_name": "New domain name", - "no_cookies_in_domain": "No cookies set for this domain", - "raw_tab": "Raw", - "set": "Set a cookie" + "cookie_expires": "Expira en", + "cookie_name": "Nombre", + "cookie_path": "Ruta", + "cookie_string": "Cookies", + "cookie_value": "Valor", + "empty_domain": "Dominio vacio", + "empty_domains": "No hay dominios", + "enter_cookie_string": "Introducir cookies", + "interceptor_no_support": "El interceptor seleccionado actualmente no admite cookies. Seleccione otro interceptor e inténtelo de nuevo.", + "managed_tab": "Gestionado", + "new_domain_name": "Nuevo nombre de dominio", + "no_cookies_in_domain": "No hay cookies para este dominio", + "raw_tab": "Sin procesar", + "set": "Establecer una cookie" } }, "count": { - "header": "Encabezado {count}", - "message": "Mensaje {count}", - "parameter": "Parámetro {count}", - "protocol": "Protocolo {count}", - "value": "Valor {cuenta}", - "variable": "Variable {count}" + "header": "{count} encabezado(s)", + "message": "{count} mensaje(s)", + "parameter": "{count} parámetro(s)", + "protocol": "{count} protocolo(s)", + "value": "{cuenta} valor(es)", + "variable": "{count} variable(es)" }, "documentation": { "generate": "Generar documentación", "generate_message": "Importar cualquier colección de Hoppscotch para generar documentación de la API sobre la marcha." }, "empty": { - "authorization": "Esta solicitud no utiliza ninguna autorización.", + "authorization": "Esta solicitud no utiliza ninguna autorización", "body": "Esta solicitud no tiene cuerpo", - "collection": "La colección está vacía", - "collections": "Las colecciones están vacías", - "documentation": "Conectarse a un punto final de GraphQL para ver la documentación", + "collection": "Colección vacía", + "collections": "No hay colecciones", + "documentation": "Es necesario conectarse a un punto final de GraphQL para ver la documentación", "endpoint": "El punto final no puede estar vacío", - "environments": "Los entornos están vacíos", - "folder": "La carpeta está vacía", + "environments": "No hay entornos", + "folder": "Carpeta vacía", "headers": "Esta solicitud no tiene encabezados", - "history": "El historial está vacío", - "invites": "La lista de invitados está vacía", - "members": "El equipo está vacío", + "history": "No hay historial", + "invites": "Lista de invitados vacía", + "members": "No hay miembros en el equipo", "parameters": "Esta solicitud no tiene ningún parámetro", "pending_invites": "No hay invitaciones pendientes para este equipo", "profile": "Iniciar sesión para ver tu perfil", - "protocols": "Los protocolos están vacíos", + "protocols": "No hay protocolos", + "request_variables": "Esta solicitud no tiene variables de solicitud", "schema": "Conectarse a un punto final de GraphQL", - "shared_requests": "Shared requests are empty", - "shared_requests_logout": "Login to view your shared requests or create a new one", - "subscription": "Subscriptions are empty", + "secret_environments": "Los secretos no están sincronizados con Hoppscotch", + "shared_requests": "No hay solicitudes compartidas", + "shared_requests_logout": "Iniciar sesión para ver sus solicitudes compartidas o crear una nueva", + "subscription": "No hay suscripciones", "team_name": "Nombre del equipo vacío", - "teams": "Los equipos están vacíos", + "teams": "No hay equipos", "tests": "No hay pruebas para esta solicitud", + "access_tokens": "No hay tokens de acceso disponibles", "shortcodes": "Aún no se han creado Shortcodes" }, "environment": { "add_to_global": "Añadir a Global", "added": "Adición al entorno", "create_new": "Crear un nuevo entorno", - "created": "Environment created", + "created": "Entorno creado", "deleted": "Eliminar el entorno", - "duplicated": "Environment duplicated", + "duplicated": "Entorno duplicado", "edit": "Editar entorno", - "empty_variables": "No variables", + "empty_variables": "No hay variables", "global": "Global", - "global_variables": "Global variables", - "import_or_create": "Import or create a environment", + "global_variables": "Variables globales", + "import_or_create": "Importar o crear un entorno", "invalid_name": "Proporciona un nombre válido para el entorno.", - "list": "Environment variables", + "list": "Variables de entorno", "my_environments": "Mis entornos", - "name": "Name", + "name": "Nombre", "nested_overflow": "las variables de entorno anidadas están limitadas a 10 niveles", "new": "Nuevo entorno", - "no_active_environment": "No active environment", + "no_active_environment": "Ningún entorno activo", "no_environment": "Sin entorno", "no_environment_description": "No se ha seleccionado ningún entorno. Elije qué hacer con las siguientes variables.", - "quick_peek": "Environment Quick Peek", - "replace_with_variable": "Replace with variable", - "scope": "Scope", + "quick_peek": "Vistazo rápido al entorno", + "replace_with_variable": "Sustituir por variable", + "scope": "Ámbito", + "secrets": "Secretos", + "secret_value": "Valor de secreto", "select": "Seleccionar entorno", - "set": "Set environment", - "set_as_environment": "Set as environment", + "set": "Establecer entorno", + "set_as_environment": "Establecer como entorno", "team_environments": "Entornos de trabajo en equipo", "title": "Entornos", "updated": "Entorno actualizado", - "value": "Value", + "value": "Valor", "variable": "Variable", - "variable_list": "Lista de variables" + "variables": "Variables", + "variable_list": "Lista de variables", + "properties": "Propiedades del entorno", + "details": "Detalles" }, "error": { - "authproviders_load_error": "Unable to load auth providers", + "authproviders_load_error": "No se han podido cargar los proveedores de autenticación", "browser_support_sse": "Este navegador no parece ser compatible con los eventos enviados por el servidor.", "check_console_details": "Consulta el registro de la consola para obtener más detalles.", - "check_how_to_add_origin": "Check how you can add an origin", + "check_how_to_add_origin": "Comprueba cómo puede añadir un origen", "curl_invalid_format": "cURL no está formateado correctamente", "danger_zone": "Zona de peligro", "delete_account": "Tu cuenta es actualmente propietaria en estos equipos:", "delete_account_description": "Para poder eliminar tu cuenta, debes darte de baja, transferir la propiedad o eliminar estos equipos.", + "empty_profile_name": "El nombre del perfil no puede estar vacío", "empty_req_name": "Nombre de solicitud vacío", "f12_details": "(F12 para más detalles)", "gql_prettify_invalid_query": "No se puede aplicar embellecedor a una consulta no válida, resuelve los errores de sintaxis de la consulta y vuelve a intentarlo", @@ -297,33 +337,42 @@ "incorrect_email": "Correo electrónico incorrecto", "invalid_link": "Enlace no válido", "invalid_link_description": "El enlace que has pulsado no es válido o ha caducado.", + "invalid_embed_link": "La inserción no existe o no es válida.", "json_parsing_failed": "JSON no válido", "json_prettify_invalid_body": "No se puede aplicar embellecedor a un cuerpo inválido, resuelve errores de sintaxis json y vuelve a intentarlo", "network_error": "Parece que hay un error de red. Por favor, inténtalo de nuevo.", "network_fail": "No se pudo enviar la solicitud", - "no_collections_to_export": "No collections to export. Please create a collection to get started.", + "no_collections_to_export": "No hay colecciones para exportar. Por favor, crea una colección para empezar.", "no_duration": "Sin duración", - "no_environments_to_export": "No environments to export. Please create an environment to get started.", + "no_environments_to_export": "No hay entornos para exportar. Por favor, crea un entorno para empezar.", "no_results_found": "No se han encontrado coincidencias", "page_not_found": "No se ha podido encontrar esta página", - "please_install_extension": "Please install the extension and add origin to the extension.", - "proxy_error": "Proxy error", + "please_install_extension": "Por favor, instala la extensión y añade el origen a la extensión.", + "proxy_error": "Error de proxy", + "same_profile_name": "El nombre del perfil actualizado es el mismo que el nombre del perfil actual", "script_fail": "No se pudo ejecutar el script de solicitud previa", "something_went_wrong": "Algo salió mal", - "test_script_fail": "No se ha podido ejecutar la secuencia de comandos posterior a la solicitud" + "test_script_fail": "No se ha podido ejecutar la secuencia de comandos posterior a la solicitud", + "reading_files": "Error al leer uno o más archivos.", + "fetching_access_tokens_list": "Algo ha ido mal al obtener la lista de tokens", + "generate_access_token": "Algo ha ido mal al generar el token de acceso", + "delete_access_token": "Algo ha ido mal al borrar el token de acceso" }, "export": { "as_json": "Exportar como JSON", "create_secret_gist": "Crear un Gist secreto", - "failed": "Something went wrong while exporting", - "gist_created": "Gist creado", + "create_secret_gist_tooltip_text": "Exportar como Gist secreto", + "failed": "Algo ha ido mal al exportar", + "secret_gist_success": "Exportado con éxito como Gist secreto", "require_github": "Iniciar sesión con GitHub para crear un Gist secreto", - "title": "Exportar" + "title": "Exportar", + "success": "Exportado con éxito", + "gist_created": "Gist creado" }, "filter": { - "all": "All", - "none": "None", - "starred": "Starred" + "all": "Todos", + "none": "Ninguno", + "starred": "Destacado" }, "folder": { "created": "Carpeta creada", @@ -334,16 +383,17 @@ "renamed": "Carpeta renombrada" }, "graphql": { - "connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?", - "connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is", - "connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is", + "connection_switch_confirm": "¿Deseas conectarte con el punto final de GraphQL más reciente?", + "connection_switch_new_url": "Al cambiar a una pestaña se desconectará de la conexión GraphQL activa. La nueva URL de conexión es", + "connection_switch_url": "Estás conectado a un punto final de GraphQL cuya URL de conexión es", "mutations": "Mutaciones", "schema": "Esquema", "subscriptions": "Suscripciones", - "switch_connection": "Switch connection" + "switch_connection": "Cambiar conexión", + "url_placeholder": "Introduce una URL de punto final de GraphQL" }, "graphql_collections": { - "title": "GraphQL Collections" + "title": "Colecciones de GraphQL" }, "group": { "time": "Tiempo", @@ -356,11 +406,11 @@ }, "helpers": { "authorization": "El encabezado de autorización se generará automáticamente cuando se envía la solicitud.", - "collection_properties_authorization": " This authorization will be set for every request in this collection.", - "collection_properties_header": "This header will be set for every request in this collection.", + "collection_properties_authorization": " Esta autorización se establecerá para cada solicitud de esta colección.", + "collection_properties_header": "Este encabezado se establecerá para cada solicitud de esta colección.", "generate_documentation_first": "Generar la documentación primero", "network_fail": "No se puede acceder a la API. Comprueba tu conexión de red y vuelve a intentarlo.", - "offline": "Parece estar desconectado. Es posible que los datos de este espacio de trabajo no estén actualizados.", + "offline": "Pareces estar desconectado. Es posible que los datos de este espacio de trabajo no estén actualizados.", "offline_short": "Pareces estar desconectado.", "post_request_tests": "Los scripts de prueba están escritos en JavaScript y se ejecutan después de recibir la respuesta.", "pre_request_script": "Los scripts previos a la solicitud están escritos en JavaScript y se ejecutan antes de que se envíe la solicitud.", @@ -377,12 +427,12 @@ "import": { "collections": "Importar colecciones", "curl": "Importar cURL", - "environments_from_gist": "Import From Gist", - "environments_from_gist_description": "Import Hoppscotch Environments From Gist", + "environments_from_gist": "Importar desde GitHub Gist", + "environments_from_gist_description": "Importar entornos Hoppscotch desde GitHub Gist", "failed": "Importación fallida", - "from_file": "Import from File", - "from_gist": "Importar desde Gist", - "from_gist_description": "Importar desde URL de Gist", + "from_file": "Importar desde archivo", + "from_gist": "Importar desde GitHub Gist", + "from_gist_description": "Importar desde URL de GitHub Gist", "from_insomnia": "Importar desde Insomnia", "from_insomnia_description": "Importar desde una colección de Insomnia", "from_json": "Importar de Hoppscotch", @@ -394,42 +444,47 @@ "from_postman": "Importar desde Postman", "from_postman_description": "Importar desde una colección de Postman", "from_url": "Importar desde una URL", - "gist_url": "Introduce la URL de Gist", - "gql_collections_from_gist_description": "Import GraphQL Collections From Gist", - "hoppscotch_environment": "Hoppscotch Environment", - "hoppscotch_environment_description": "Import Hoppscotch Environment JSON file", - "import_from_url_invalid_fetch": "Couldn't get data from the url", - "import_from_url_invalid_file_format": "Error while importing collections", - "import_from_url_invalid_type": "Unsupported type. accepted values are 'hoppscotch', 'openapi', 'postman', 'insomnia'", - "import_from_url_success": "Collections Imported", - "insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file", + "gist_url": "Introduce la URL del GitHub Gist", + "gql_collections_from_gist_description": "Importar colecciones GraphQL desde GitHub Gist", + "hoppscotch_environment": "Entorno de Hoppscotch", + "hoppscotch_environment_description": "Importar archivo JSON del entorno de Hoppscotch", + "import_from_url_invalid_fetch": "No se han podido obtener datos de la url", + "import_from_url_invalid_file_format": "Error al importar colecciones", + "import_from_url_invalid_type": "Tipo no admitido. Los valores aceptados son \"hoppscotch\", \"openapi\", \"postman\", \"insomnia\".", + "import_from_url_success": "Colecciones Importadas", + "insomnia_environment_description": "Importar el entorno de Insomnia desde un archivo JSON/YAML", "json_description": "Importar colecciones desde un archivo JSON de colecciones de Hoppscotch", - "postman_environment": "Postman Environment", - "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Importar" + "postman_environment": "Entorno de Postman", + "postman_environment_description": "Importar entorno de Postman desde un archivo JSON", + "title": "Importar", + "file_size_limit_exceeded_warning_multiple_files": "Los archivos seleccionados exceden el límite recomendado de 10MB. Sólo se importarán los primeros {files} seleccionados.", + "file_size_limit_exceeded_warning_single_file": "El archivo seleccionado supera el límite recomendado de 10 MB. Por favor, selecciona otro archivo.", + "success": "Importado con éxito" }, "inspections": { - "description": "Inspect possible errors", + "description": "Inspeccionar posibles errores", "environment": { - "add_environment": "Add to Environment", - "not_found": "Environment variable “{environment}” not found." + "add_environment": "Añadir al Entorno", + "add_environment_value": "Add value", + "empty_value": "El valor de la variable de entorno '{variable}' está vacío ", + "not_found": "No se ha encontrado la variable de entorno \"{environment}\"." }, "header": { - "cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." + "cookie": "El navegador no permite que Hoppscotch establezca el encabezado Cookie. Mientras trabajamos en la aplicación de escritorio de Hoppscotch (próximamente), por favor utilice el encabezado de autorización en su lugar." }, "response": { - "401_error": "Please check your authentication credentials.", - "404_error": "Please check your request URL and method type.", - "cors_error": "Please check your Cross-Origin Resource Sharing configuration.", - "default_error": "Please check your request.", - "network_error": "Please check your network connection." + "401_error": "Compruebe tus credenciales de autenticación.", + "404_error": "Compruebe la URL de su solicitud y el tipo de método.", + "cors_error": "Por favor, comprueba tu configuración de Compartición de Recursos \"Cross-Origin\".", + "default_error": "Por favor, comprueba tu solicitud.", + "network_error": "Comprueba tu conexión de red." }, - "title": "Inspector", + "title": "Inspeccionador", "url": { - "extension_not_installed": "Extension not installed.", - "extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", - "extention_enable_action": "Enable Browser Extension", - "extention_not_enabled": "Extension not enabled." + "extension_not_installed": "Extensión no instalada.", + "extension_unknown_origin": "Asegúrate de haber agregado el origen del punto final de la API a la lista de Extensiones del Navegador Hoppscotch.", + "extention_enable_action": "Activar la extensión del navegador", + "extention_not_enabled": "Extensión no habilitada." } }, "layout": { @@ -443,10 +498,10 @@ "close_unsaved_tab": "Tienes cambios sin guardar", "collections": "Colecciones", "confirm": "Confirmar", - "customize_request": "Customize Request", + "customize_request": "Personalizar solicitud", "edit_request": "Editar solicitud", "import_export": "Importación y exportación", - "share_request": "Share Request" + "share_request": "Compartir solicitud" }, "mqtt": { "already_subscribed": "Ya estás suscrito a este tema.", @@ -459,7 +514,7 @@ "connection_config": "Configuración de conexión", "connection_not_authorized": "Esta conexión MQTT no utiliza ninguna autenticación.", "invalid_topic": "Indica un tema para la suscripción", - "keep_alive": "Mantenerse vivo", + "keep_alive": "Mantenerse activo", "log": "Registro", "lw_message": "Mensaje de última voluntad", "lw_qos": "QoS de última voluntad", @@ -494,7 +549,7 @@ }, "profile": { "app_settings": "Ajustes de la aplicación", - "default_hopp_displayname": "Unnamed User", + "default_hopp_displayname": "Usuario anónimo", "editor": "Editor", "editor_description": "Los editores pueden añadir, editar y eliminar solicitudes.", "email_verification_mail": "Se ha enviado un correo electrónico de verificación a tu dirección de correo electrónico. Haz clic en el enlace para verificar tu dirección de correo electrónico.", @@ -527,12 +582,12 @@ "enter_curl": "Ingrese cURL", "generate_code": "Generar código", "generated_code": "Código generado", - "go_to_authorization_tab": "Go to Authorization tab", - "go_to_body_tab": "Go to Body tab", + "go_to_authorization_tab": "Ir a la pestaña Autorización", + "go_to_body_tab": "Ir a la pestaña de cuerpo de solicitud", "header_list": "Lista de encabezados", "invalid_name": "Proporciona un nombre para la solicitud.", "method": "Método", - "moved": "Request moved", + "moved": "Solicitud movida", "name": "Nombre de solicitud", "new": "Nueva solicitud", "order_changed": "Orden de solicitudes actualizadas", @@ -544,20 +599,22 @@ "path": "Ruta", "payload": "Carga útil", "query": "Consulta", - "raw_body": "Cuerpo de solicitud sin procesar", - "rename": "Rename Request", + "raw_body": "cuerpo sin procesar", + "rename": "Renombrar solicitud", "renamed": "Solicitud renombrada", + "request_variables": "Variables de solicitud", "run": "Ejecutar", "save": "Guardar", "save_as": "Guardar como", "saved": "Solicitud guardada", "share": "Compartir", "share_description": "Comparte Hoppscotch con tus amigos", - "share_request": "Share Request", - "stop": "Stop", + "share_request": "Compartir solicitud", + "stop": "Detener", "title": "Solicitud", "type": "Tipo de solicitud", "url": "URL", + "url_placeholder": "Introduce una URL o pega un comando cURL", "variables": "Variables", "view_my_links": "Ver mis enlaces", "copy_link": "Copiar enlace" @@ -572,7 +629,7 @@ "json": "JSON", "pdf": "PDF", "preview_html": "Vista previa de HTML", - "raw": "Crudo", + "raw": "Sin procesar", "size": "Tamaño", "status": "Estado", "time": "Tiempo", @@ -636,29 +693,29 @@ "verify_email": "Verificar correo electrónico" }, "shared_requests": { - "button": "Button", - "button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.", - "copy_html": "Copy HTML", - "copy_link": "Copy Link", - "copy_markdown": "Copy Markdown", - "creating_widget": "Creating widget", - "customize": "Customize", - "deleted": "Shared request deleted", - "description": "Select a widget, you can change and customize this later", - "embed": "Embed", - "embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.", - "link": "Link", - "link_info": "Create a shareable link to share with anyone on the internet with view access.", - "modified": "Shared request modified", - "not_found": "Shared request not found", - "open_new_tab": "Open in new tab", - "preview": "Preview", - "run_in_hoppscotch": "Run in Hoppscotch", + "button": "Botón", + "button_info": "Crea un botón \"Ejecutar en Hoppscotch\" para tu página web, blog o un README.", + "copy_html": "Copiar HTML", + "copy_link": "Copiar enlace", + "copy_markdown": "Copiar Markdown", + "creating_widget": "Crear widget", + "customize": "Personalizar", + "deleted": "Solicitud compartida eliminada", + "description": "Selecciona un widget, puedes cambiarlo y personalizarlo más tarde", + "embed": "Incrustar", + "embed_info": "Añada un mini \"Hoppscotch API Playground\" a tu sitio web, blog o documentación.", + "link": "Enlace", + "link_info": "Crea un enlace compartible para compartirlo con cualquier persona en Internet con acceso de visualización.", + "modified": "Solicitud compartida modificada", + "not_found": "Solicitud compartida no encontrada", + "open_new_tab": "Abrir en una nueva pestaña", + "preview": "Vista previa", + "run_in_hoppscotch": "Ejecutar en Hoppscotch", "theme": { - "dark": "Dark", - "light": "Light", - "system": "System", - "title": "Theme" + "dark": "Oscuro", + "light": "Claro", + "system": "Sistema", + "title": "Tema" } }, "shortcut": { @@ -685,8 +742,8 @@ "title": "Navegación" }, "others": { - "prettify": "Prettify Editor's Content", - "title": "Others" + "prettify": "Formatear el contenido del editor", + "title": "Otros" }, "request": { "delete_method": "Seleccionar método DELETE", @@ -698,13 +755,13 @@ "post_method": "Seleccionar método POST", "previous_method": "Seleccionar método anterior", "put_method": "Seleccionar método PUT", - "rename": "Rename Request", + "rename": "Renombrar solicitud", "reset_request": "Solicitud de reinicio", - "save_request": "Save Request", + "save_request": "Guardar solicitud", "save_to_collections": "Guardar en colecciones", "send_request": "Enviar solicitud", - "share_request": "Share Request", - "show_code": "Generate code snippet", + "share_request": "Compartir solicitud", + "show_code": "Generar fragmento de código", "title": "Solicitud", "copy_request_link": "Copiar enlace de solicitud" }, @@ -723,95 +780,102 @@ }, "show": { "code": "Mostrar código", - "collection": "Expand Collection Panel", + "collection": "Ampliar el panel de colecciones", "more": "Mostrar más", "sidebar": "Mostrar barra lateral" }, "socketio": { "communication": "Comunicación", - "connection_not_authorized": "This SocketIO connection does not use any authentication.", + "connection_not_authorized": "Esta conexión SocketIO no utiliza ningún tipo de autenticación.", "event_name": "Nombre del evento", "events": "Eventos", "log": "Registro", "url": "URL" }, "spotlight": { - "change_language": "Change Language", + "change_language": "Cambiar idioma", "environments": { - "delete": "Delete current environment", - "duplicate": "Duplicate current environment", - "duplicate_global": "Duplicate global environment", - "edit": "Edit current environment", - "edit_global": "Edit global environment", - "new": "Create new environment", - "new_variable": "Create a new environment variable", - "title": "Environments" + "delete": "Borrar el entorno actual", + "duplicate": "Duplicar el entorno actual", + "duplicate_global": "Entorno global duplicado", + "edit": "Editar el entorno actual", + "edit_global": "Editar el entorno global", + "new": "Crear un nuevo entorno", + "new_variable": "Crear una nueva variable de entorno", + "title": "Entornos" }, "general": { - "chat": "Chat with support", - "help_menu": "Help and support", - "open_docs": "Read Documentation", - "open_github": "Open GitHub repository", - "open_keybindings": "Keyboard shortcuts", + "chat": "Chatear con el servicio de asistencia", + "help_menu": "Ayuda y asistencia", + "open_docs": "Leer la documentación", + "open_github": "Abrir repositorio de GitHub", + "open_keybindings": "Atajos de teclado", "social": "Social", "title": "General" }, "graphql": { - "connect": "Connect to server", - "disconnect": "Disconnect from server" + "connect": "Conectarse al servidor", + "disconnect": "Desconectarse del servidor" }, "miscellaneous": { - "invite": "Invite your friends to Hoppscotch", - "title": "Miscellaneous" + "invite": "Invita a tus amigos a Hoppscotch", + "title": "Varios" }, "request": { - "save_as_new": "Save as new request", - "select_method": "Select method", - "switch_to": "Switch to", - "tab_authorization": "Authorization tab", - "tab_body": "Body tab", - "tab_headers": "Headers tab", - "tab_parameters": "Parameters tab", - "tab_pre_request_script": "Pre-request script tab", - "tab_query": "Query tab", - "tab_tests": "Tests tab", - "tab_variables": "Variables tab" + "save_as_new": "Guardar como nueva solicitud", + "select_method": "Seleccionar método", + "switch_to": "Cambiar a", + "tab_authorization": "Pestaña de autorización", + "tab_body": "Pestaña de cuerpo", + "tab_headers": "Pestaña de encabezados", + "tab_parameters": "Pestaña de parámetros", + "tab_pre_request_script": "Pestaña del script de pre-solicitud", + "tab_query": "Pestaña de consulta", + "tab_tests": "Pestaña de pruebas", + "tab_variables": "Pestaña de variables" }, "response": { - "copy": "Copy response", - "download": "Download response as file", - "title": "Response" + "copy": "Copiar respuesta", + "download": "Descargar la respuesta como archivo", + "title": "Respuesta" }, "section": { "interceptor": "Interceptor", - "interface": "Interface", - "theme": "Theme", - "user": "User" + "interface": "Interfaz", + "theme": "Tema", + "user": "Usuario" }, "settings": { - "change_interceptor": "Change Interceptor", - "change_language": "Change Language", + "change_interceptor": "Cambiar Interceptor", + "change_language": "Cambiar idioma", "theme": { - "black": "Black", - "dark": "Dark", - "light": "Light", - "system": "System preference" + "black": "Negro", + "dark": "Oscuro", + "light": "Claro", + "system": "Preferencia del sistema" } }, "tab": { - "close_current": "Close current tab", - "close_others": "Close all other tabs", - "duplicate": "Duplicate current tab", - "new_tab": "Open a new tab", - "title": "Tabs" + "close_current": "Cerrar la pestaña actual", + "close_others": "Cerrar todas las demás pestañas", + "duplicate": "Duplicar pestaña actual", + "new_tab": "Abrir una nueva pestaña", + "title": "Pestañas" }, "workspace": { - "delete": "Delete current team", - "edit": "Edit current team", - "invite": "Invite people to team", - "new": "Create new team", - "switch_to_personal": "Switch to your personal workspace", - "title": "Teams" + "delete": "Borrar el espacio de trabajo actual", + "edit": "Editar el espacio de trabajo actual", + "invite": "Invitar al espacio de trabajo", + "new": "Crear un nuevo espacio de trabajo", + "switch_to_personal": "Cambia a tu espacio de trabajo personal", + "title": "Espacio de trabajo" + }, + "phrases": { + "try": "Probar", + "import_collections": "Importar colecciones", + "create_environment": "Crear entorno", + "create_workspace": "Crear espacio de trabajo", + "share_request": "Compartir solicitud" } }, "sse": { @@ -826,10 +890,10 @@ "connected": "Conectado", "connected_to": "Conectado a {name}", "connecting_to": "Conectando con {name}...", - "connection_error": "Failed to connect", - "connection_failed": "Error de conexión", + "connection_error": "Error de conexión", + "connection_failed": "Conexión fallida", "connection_lost": "Conexión perdida", - "copied_interface_to_clipboard": "Copied {language} interface type to clipboard", + "copied_interface_to_clipboard": "Copiado tipo de interfaz {language} al portapapeles", "copied_to_clipboard": "Copiado al portapapeles", "deleted": "Eliminado", "deprecated": "OBSOLETO", @@ -837,21 +901,21 @@ "disconnected": "Desconectado", "disconnected_from": "Desconectado de {name}", "docs_generated": "Documentación generada", - "download_failed": "Download failed", + "download_failed": "Descarga fallida", "download_started": "Descarga iniciada", "enabled": "Activado", "file_imported": "Archivo importado", - "finished_in": "Terminado en {duration} ms", - "hide": "Hide", + "finished_in": "Terminado en {duration}ms", + "hide": "Ocultar", "history_deleted": "Historial eliminado", "linewrap": "Envolver líneas", "loading": "Cargando...", - "message_received": "Mensaje: {mensaje} llegó sobre el tema: {topic}", + "message_received": "Mensaje: llegó {message} al: {topic}", "mqtt_subscription_failed": "Algo ha ido mal al suscribirse al tema: {topic}", "none": "Ninguno", "nothing_found": "Nada encontrado para", - "published_error": "Algo ha ido mal al publicar el mensaje: {topic} al tema: {message}", - "published_message": "Mensaje publicado: {mensaje} al tema: {topic}", + "published_error": "Algo ha ido mal al publicar el mensaje: {message} al tema: {topic}", + "published_message": "Mensaje publicado: {message} al tema: {topic}", "reconnection_error": "Fallo en la reconexión", "show": "Show", "subscribed_failed": "Error al suscribirse al tema: {topic}", @@ -868,19 +932,19 @@ "forum": "Haz preguntas y obtén respuestas", "github": "Síguenos en Github", "shortcuts": "Navega por la aplicación más rápido", - "team": "Ponte en contacto con el equipo", "title": "Ayuda", - "twitter": "Síguenos en Twitter" + "twitter": "Síguenos en Twitter", + "team": "Ponte en contacto con el equipo" }, "tab": { "authorization": "Autorización", "body": "Cuerpo", - "close": "Close Tab", - "close_others": "Close other Tabs", + "close": "Cerrar pestaña", + "close_others": "Cerrar otras pestañas", "collections": "Colecciones", "documentation": "Documentación", - "duplicate": "Duplicate Tab", - "environments": "Environments", + "duplicate": "Duplicar pestaña", + "environments": "Entornos", "headers": "Encabezados", "history": "Historial", "mqtt": "MQTT", @@ -889,7 +953,10 @@ "queries": "Consultas", "query": "Consulta", "schema": "Esquema", - "shared_requests": "Shared Requests", + "shared_requests": "Solicitudes compartidas", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Pruebas", @@ -898,60 +965,64 @@ "websocket": "WebSocket" }, "team": { - "already_member": "Ya eres miembro de este equipo. Ponte en contacto con el propietario de tu equipo.", - "create_new": "Crear nuevo equipo", - "deleted": "Equipo eliminado", - "edit": "Editar equipo", + "already_member": "Ya eres miembro de este espacio de trabajo. Ponte en contacto con el propietario del espacio de trabajo.", + "create_new": "Crear nuevo espacio de trabajo", + "deleted": "Espacio de trabajo eliminado", + "edit": "Editar espacio de trabajo", "email": "Correo electrónico", - "email_do_not_match": "El correo electrónico no coincide con los datos de tu cuenta. Ponte en contacto con el propietario de tu equipo.", - "exit": "Salir del equipo", - "exit_disabled": "Solo el propietario puede salir del equipo", - "failed_invites": "Failed invites", + "email_do_not_match": "El correo electrónico no coincide con los datos de tu cuenta. Ponte en contacto con el propietario del espacio de trabajo.", + "exit": "Salir del espacio de trabajo", + "exit_disabled": "Sólo el propietario no puede salir del espacio de trabajo", + "failed_invites": "Invitaciones fallidas", "invalid_coll_id": "Identificador de colección no válido", "invalid_email_format": "El formato de correo electrónico no es válido", - "invalid_id": "Identificador de equipo inválido. Ponte en contacto con el propietario de tu equipo.", + "invalid_id": "Identificador de espacio de trabajo inválido. Ponte en contacto con el propietario del espacio de trabajo.", "invalid_invite_link": "Enlace de invitación inválido", - "invalid_invite_link_description": "El enlace que has seguido no es válido. Ponte en contacto con el propietario de tu equipo.", - "invalid_member_permission": "Proporcionar un permiso válido al miembro del equipo", + "invalid_invite_link_description": "El enlace que has seguido no es válido. Ponte en contacto con el propietario del espacio de trabajo.", + "invalid_member_permission": "Por favor, proporciona un permiso válido al miembro del espacio de trabajo", "invite": "Invitar", "invite_more": "Invitar a más", - "invite_tooltip": "Invite a personas a este espacio de trabajo", - "invited_to_team": "{owner} te ha invitado a unirte al {tema}", + "invite_tooltip": "Invitar a personas a este espacio de trabajo", + "invited_to_team": "{owner} te ha invitado al espacio de trabajo {team}", "join": "Invitación aceptada", - "join_beta": "Únete al programa beta para acceder a los equipos.", - "join_team": "Entrar a {team}", - "joined_team": "Haz entrado a {team}", - "joined_team_description": "Ahora eres miembro de este equipo", - "left": "Saliste del equipo", + "join_team": "Entrar al espacio de trabajo {team}", + "joined_team": "Haz entrado al espacio de trabajo {team}", + "joined_team_description": "Ahora eres miembro de este espacio de trabajo", + "left": "Saliste del espacio de trabajo", "login_to_continue": "Iniciar sesión para continuar", - "login_to_continue_description": "Tienes que estar conectado para unirte a un equipo.", + "login_to_continue_description": "Tienes que estar conectado para unirte a un espacio de trabajo.", "logout_and_try_again": "Cerrar la sesión e iniciar sesión con otra cuenta", - "member_has_invite": "Este Identificador de correo electrónico ya tiene una invitación. Ponte en contacto con el propietario de tu equipo.", - "member_not_found": "Miembro no encontrado. Ponte en contacto con el propietario de tu equipo.", - "member_removed": "Usuario eliminado", + "member_has_invite": "Este identificador de correo electrónico ya tiene una invitación. Ponte en contacto con el propietario del espacio de trabajo.", + "member_not_found": "Miembro no encontrado. Ponte en contacto con el propietario del espacio de trabajo.", + "member_removed": "Miembro eliminado", "member_role_updated": "Funciones de usuario actualizadas", "members": "Miembros", - "more_members": "+{count} more", - "name_length_insufficient": "El nombre del equipo debe tener al menos 6 caracteres", - "name_updated": "Nombre de equipo actualizado", - "new": "Nuevo equipo", - "new_created": "Nuevo equipo creado", - "new_name": "Mi nuevo equipo", + "more_members": "+{count} más", + "name_length_insufficient": "El nombre del espacio de trabajo debe tener al menos 6 caracteres", + "name_updated": "Nombre de espacio de trabajo actualizado", + "new": "Nuevo espacio de trabajo", + "new_created": "Nuevo espacio de trabajo creado", + "new_name": "Mi nuevo espacio de trabajo", "no_access": "No tienes acceso de edición a estas colecciones.", - "no_invite_found": "No se ha encontrado la invitación. Ponte en contacto con el propietario de tu equipo.", + "no_invite_found": "No se ha encontrado la invitación. Ponte en contacto con el propietario del espacio de trabajo.", "no_request_found": "Solicitud no encontrada.", - "not_found": "Equipo no encontrado. Ponte en contacto con el propietario de tu equipo.", - "not_valid_viewer": "No eres un espectador válido. Ponte en contacto con el propietario de tu equipo.", + "not_found": "Espacio de trabajo no encontrado. Ponte en contacto con el propietario del espacio de trabajo.", + "not_valid_viewer": "No eres un espectador válido. Ponte en contacto con el propietario del espacio de trabajo.", "parent_coll_move": "No se puede mover la colección a una colección hija", "pending_invites": "Invitaciones pendientes", "permissions": "Permisos", - "same_target_destination": "Same target and destination", - "saved": "Equipo guardado", - "select_a_team": "Seleccionar un equipo", - "success_invites": "Success invites", - "title": "Equipos", + "same_target_destination": "Mismo objetivo y destino", + "saved": "Espacio de trabajo guardado", + "select_a_team": "Seleccionar un espacio de trabajo", + "success_invites": "Invitaciones realizadas con éxito", + "title": "Espacios de trabajo", "we_sent_invite_link": "¡Hemos enviado un enlace de invitación a todos los invitados!", - "we_sent_invite_link_description": "Pide a todos los invitados que revisen tu bandeja de entrada. Haz clic en el enlace para unirse al equipo." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Pide a todos los invitados que revisen su bandeja de entrada. Tienen que hacer clic en el enlace para unirse al espacio de trabajo.", + "invite_sent_smtp_disabled_description": "El envío de correos electrónicos de invitación está deshabilitado para esta instancia de Hoppscotch. Utiliza el botón Copiar enlace para copiar y compartir el enlace de invitación manualmente.", + "copy_invite_link": "Copiar enlace de invitación", + "search_title": "Solicitudes del espacio de trabajo", + "join_beta": "Únete al programa beta para acceder a los espacio de trabajos." }, "team_environment": { "deleted": "Entorno eliminado", @@ -978,16 +1049,57 @@ "workspace": { "change": "Cambiar el espacio de trabajo", "personal": "Mi espacio de trabajo", + "other_workspaces": "My Workspaces", "team": "Espacio de trabajo en equipo", "title": "Espacios de trabajo" }, + "site_protection": { + "login_to_continue": "Iniciar sesión para continuar", + "login_to_continue_description": "Debes iniciar sesión para acceder a esta instancia de Hoppscotch Enterprise.", + "error_fetching_site_protection_status": "Algo ha fallado al obtener el estado de protección del sitio web" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Tokens de acceso personal", + "section_description": "Los tokens de acceso personal actualmente te ayudan a conectar el CLI a tu cuenta Hoppscotch", + "last_used_on": "Utilizado por última vez en", + "expires_on": "Expira en", + "no_expiration": "Sin expiración", + "expired": "Expirado", + "copy_token_warning": "Asegúrate de copiar ahora tu token de acceso personal. No podrás volver a verlo.", + "token_purpose": "¿Para qué es este token?", + "expiration_label": "Expiración", + "scope_label": "Ámbito", + "workspace_read_only_access": "Acceso de sólo lectura a los datos del espacio de trabajo.", + "personal_workspace_access_limitation": "Los tokens de acceso personal no pueden acceder a tu espacio de trabajo personal.", + "generate_token": "Generar token", + "invalid_label": "Proporciona un nombre válido para el token", + "no_expiration_verbose": "Este token no expirará nunca.", + "token_expires_on": "Este token expirará el", + "generate_new_token": "Generar nuevo token", + "generate_modal_title": "Nuevo token de acceso personal", + "deletion_success": "El token de acceso {label} ha sido eliminado" + }, + "collection_runner": { + "collection_id": "ID de colección", + "environment_id": "ID de entorno", + "cli_collection_id_description": "Este ID de colección será utilizado por el CLI collection runner para Hoppscotch.", + "cli_environment_id_description": "Este ID de entorno será utilizado por el CLI collection runner para Hoppscotch.", + "include_active_environment": "Incluir un entorno activo:", + "cli": "CLI", + "ui": "Runner (próximamente)", + "cli_command_generation_description_cloud": "Copia el siguiente comando y ejecútalo desde la CLI. Por favor, especifica un token de acceso personal.", + "cli_command_generation_description_sh": "Copia el siguiente comando y ejecútalo desde la CLI. Por favor, especifica un token de acceso personal y verifica la URL generada del servidor de instancias SH.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copia el siguiente comando y ejecútalo desde la CLI. Por favor, especifica un token de acceso personal y la URL del servidor de instancias SH.", + "run_collection": "Ejecutar colección" + }, "shortcodes": { "actions": "Acciones", "created_on": "Creado el", "deleted": "Código corto eliminado", "method": "Método", "not_found": "Shortcode no encontrado", - "short_code": "Short code", + "short_code": "Shortcode", "url": "URL" } } diff --git a/packages/hoppscotch-common/locales/fi.json b/packages/hoppscotch-common/locales/fi.json index 4d97a52615..513152af2a 100644 --- a/packages/hoppscotch-common/locales/fi.json +++ b/packages/hoppscotch-common/locales/fi.json @@ -24,8 +24,10 @@ "go_back": "Mene takaisin", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Etiketti", "learn_more": "Lue lisää", + "download_here": "Download here", "less": "Less", "more": "Lisää", "new": "Uusi", @@ -43,6 +45,7 @@ "search": "Hae", "send": "Lähettää", "share": "Share", + "show_secret": "Show secret", "start": "alkaa", "starting": "Starting", "stop": "Lopettaa", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Kirjoita komento tai hae…", "we_use_cookies": "Käytämme evästeitä", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Mikä on uutta?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Tili on eri kirjautumistiedoilla - Kirjaudu linkittääksesi molemmat tilit", "all_sign_in_options": "Kaikki kirjautumisvaihtoehdot", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Jatka sähköpostilla", "continue_with_github": "Jatka GitHubilla", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Jatka Googlella", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "Jatka OIDC:n kanssa", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Salasana", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Token", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Muokkaa kokoelmaa", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Anna kokoelmalle kelvollinen nimi", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Valitse kokoelma", "select_location": "Valitse sijainti", + "details": "Details", "select_team": "Valitse joukkue", "team_collections": "Joukkuekokoelmat" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Haluatko varmasti poistaa telemetrian käytöstä?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Haluatko varmasti synkronoida tämän työtilan?" + "sync": "Haluatko varmasti synkronoida tämän työtilan?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "There are no pending invites for this team", "profile": "Login to view your profile", "protocols": "Protokollat ovat tyhjiä", + "request_variables": "This request does not have any request variables", "schema": "Muodosta yhteys GraphQL -päätepisteeseen", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Joukkueen nimi tyhjä", "teams": "Joukkueet ovat tyhjiä", "tests": "Tätä pyyntöä ei ole testattu", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Valitse ympäristö", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment updation", "value": "Value", "variable": "Variable", - "variable_list": "Muuttujien luettelo" + "variables": "Variables", + "variable_list": "Muuttujien luettelo", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Tyhjä pyynnön nimi", "f12_details": "(F12 lisätietoja)", "gql_prettify_invalid_query": "Virheellistä kyselyä ei voitu määrittää, ratkaista kyselyn syntaksivirheet ja yrittää uudelleen", @@ -297,6 +337,7 @@ "incorrect_email": "Incorrect email", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "Virheellistä runkoa ei voitu määrittää, ratkaista json -syntaksivirheitä ja yrittää uudelleen", "network_error": "There seems to be a network error. Please try again.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Ennakkopyyntöskriptiä ei voitu suorittaa", "something_went_wrong": "Jotain meni pieleen", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Vie JSON -muodossa", "create_secret_gist": "Luo salainen ydin", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist luotu", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Kirjaudu sisään GitHubilla luodaksesi salaisen sisällön", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Gist luotu" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutaatiot", "schema": "Kaavio", "subscriptions": "Tilaukset", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Tuonti" + "title": "Tuonti", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Raaka pyynnön runko", "rename": "Rename Request", "renamed": "Pyyntö nimettiin uudelleen", + "request_variables": "Request variables", "run": "Juosta", "save": "Tallentaa", "save_as": "Tallenna nimellä", @@ -558,6 +614,7 @@ "title": "Pyyntö", "type": "Pyynnön tyyppi", "url": "URL -osoite", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Muuttujat", "view_my_links": "View my links", "copy_link": "Kopioi linkki" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Esitä kysymyksiä ja saat vastauksia", "github": "Follow us on Github", "shortcuts": "Selaa sovellusta nopeammin", - "team": "Ota yhteyttä tiimiin", "title": "Tuki", - "twitter": "Seuraa meitä Twitterissä" + "twitter": "Seuraa meitä Twitterissä", + "team": "Ota yhteyttä tiimiin" }, "tab": { "authorization": "Valtuutus", @@ -890,6 +954,9 @@ "query": "Kysely", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Pistorasia.IO", "sse": "SSE", "tests": "Testit", @@ -918,7 +985,6 @@ "invite_tooltip": "Invite people to this workspace", "invited_to_team": "{owner} invited you to join {team}", "join": "Invitation accepted", - "join_beta": "Liity beta -ohjelmaan päästäksesi tiimeihin.", "join_team": "Join {team}", "joined_team": "You have joined {team}", "joined_team_description": "You are now a member of this team", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Joukkueet", "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Liity beta -ohjelmaan päästäksesi tiimeihin." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/fr.json b/packages/hoppscotch-common/locales/fr.json index a35b5f0e8d..e47208db42 100644 --- a/packages/hoppscotch-common/locales/fr.json +++ b/packages/hoppscotch-common/locales/fr.json @@ -1,15 +1,15 @@ { "action": { - "add": "Add", - "autoscroll": "Autoscroll", + "add": "Ajouter", + "autoscroll": "Auto-scroll", "cancel": "Annuler", "choose_file": "Choisir un fichier", "clear": "Effacer", "clear_all": "Tout effacer", - "clear_history": "Clear all History", - "close": "Close", + "clear_history": "Effacer tout l'historique", + "close": "Fermer", "connect": "Connecter", - "connecting": "Connecting", + "connecting": "Connexion", "copy": "Copier", "create": "Create", "delete": "Supprimer", @@ -22,10 +22,12 @@ "edit": "Éditer", "filter": "Filter", "go_back": "Retour", - "go_forward": "Go forward", - "group_by": "Group by", + "go_forward": "Avancer", + "group_by": "Grouper par", + "hide_secret": "Hide secret", "label": "Étiqueter", "learn_more": "En savoir plus", + "download_here": "Download here", "less": "Moins", "more": "Suite", "new": "Nouveau", @@ -35,16 +37,17 @@ "prettify": "Formater", "properties": "Properties", "remove": "Supprimer", - "rename": "Rename", + "rename": "Renommer", "restore": "Restaurer", "save": "Sauvegarder", - "scroll_to_bottom": "Scroll to bottom", - "scroll_to_top": "Scroll to top", + "scroll_to_bottom": "Défiler vers le bas", + "scroll_to_top": "Défiler vers le haut", "search": "Chercher", "send": "Envoyer", "share": "Share", + "show_secret": "Show secret", "start": "Démarrer", - "starting": "Starting", + "starting": "Démarrage", "stop": "Arrêter", "to_close": "pour fermer", "to_navigate": "pour naviguer", @@ -86,8 +89,8 @@ "search": "Chercher", "share": "Partager", "shortcuts": "Raccourcis", - "social_description": "Follow us on social media to stay updated with the latest news, updates and releases.", - "social_links": "Social links", + "social_description": "Suivez-nous sur les médias sociaux pour rester informé des dernières nouvelles, mises à jour et communiqués.", + "social_links": "Liens sociaux", "spotlight": "Projecteur", "status": "Statut", "status_description": "Vérifier l'état du site web", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Tapez une commande ou recherchez…", "we_use_cookies": "Nous utilisons des cookies", - "whats_new": "Quoi de neuf?", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", + "whats_new": "Quoi de neuf ?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Le compte existe avec des informations d'identification différentes - Connectez-vous pour lier les deux comptes", "all_sign_in_options": "Toutes les options de connexion", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Continuer par e-mail", "continue_with_github": "Continuer avec GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Continuer avec Google", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "Continuer avec OIDC", @@ -120,25 +127,46 @@ }, "authorization": { "generate_token": "Générer un jeton", - "graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", + "graphql_headers": "Les en-têtes d'autorisation sont envoyés en tant que partie de la charge utile de connection_init.", "include_in_url": "Inclure dans l'URL", "inherited_from": "Inherited from {auth} from Parent Collection {collection} ", "learn": "Apprendre comment", "oauth": { - "redirect_auth_server_returned_error": "Auth Server returned an error state", - "redirect_auth_token_request_failed": "Request to get the auth token failed", - "redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token", - "redirect_invalid_state": "Invalid State value present in the redirect", - "redirect_no_auth_code": "No Authorization Code present in the redirect", - "redirect_no_client_id": "No Client ID defined", - "redirect_no_client_secret": "No Client Secret Defined", - "redirect_no_code_verifier": "No Code Verifier Defined", - "redirect_no_token_endpoint": "No Token Endpoint Defined", - "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", - "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "redirect_auth_server_returned_error": "Le serveur d'authentification a renvoyé un état d'erreur", + "redirect_auth_token_request_failed": "La demande d'obtention du jeton d'authentification a échoué", + "redirect_auth_token_request_invalid_response": "Réponse invalide du point de terminaison Token lors de la demande d'un jeton d'authentification", + "redirect_invalid_state": "Valeur d'état non valide présente dans la redirection", + "redirect_no_auth_code": "Pas de code d'autorisation dans la redirection", + "redirect_no_client_id": "Pas d'ID client défini", + "redirect_no_client_secret": "Pas de secret client défini", + "redirect_no_code_verifier": "Pas de vérificateur de code défini", + "redirect_no_token_endpoint": "Aucun point de terminaison de jeton n'est défini", + "something_went_wrong_on_oauth_redirect": "Quelque chose s'est mal passé lors de la redirection OAuth", + "something_went_wrong_on_token_generation": "Un problème s'est produit lors de la génération des jetons", + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Mot de passe", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Jeton", @@ -147,12 +175,13 @@ }, "collection": { "created": "Collection créée", - "different_parent": "Cannot reorder collection with different parent", + "different_parent": "Impossible de réorganiser une collection dont le parent est différent", "edit": "Modifier la collection", - "import_or_create": "Import or create a collection", + "import_or_create": "Importer ou créer une collection", + "import_collection": "Import Collection", "invalid_name": "Veuillez fournir un nom valide pour la collection", - "invalid_root_move": "Collection already in the root", - "moved": "Moved Successfully", + "invalid_root_move": "Collection déjà présente dans la racine", + "moved": "Déplacement réussi", "my_collections": "Mes collections", "name": "Ma nouvelle collection", "name_length_insufficient": "Le nom de la collection doit comporter au moins 3 caractères", @@ -163,16 +192,17 @@ "renamed": "Collection renommée", "request_in_use": "Demande en cours d'utilisation", "save_as": "Enregistrer sous", - "save_to_collection": "Save to Collection", + "save_to_collection": "Enregistrer dans la collection", "select": "Sélectionnez une collection", "select_location": "Sélectionnez l'emplacement", + "details": "Details", "select_team": "Sélectionnez une équipe", "team_collections": "Collections de l'équipe" }, "confirm": { - "close_unsaved_tab": "Are you sure you want to close this tab?", - "close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", - "exit_team": "Are you sure you want to leave this team?", + "close_unsaved_tab": "Êtes-vous sûr de vouloir fermer cet onglet ?", + "close_unsaved_tabs": "Êtes-vous sûr de vouloir fermer tous les onglets ? {count} onglets non enregistrés seront perdus", + "exit_team": "Êtes-vous sûr de vouloir quitter cette équipe ?", "logout": "Êtes-vous sûr de vouloir vous déconnecter?", "remove_collection": "Voulez-vous vraiment supprimer définitivement cette collection ?", "remove_environment": "Voulez-vous vraiment supprimer définitivement cet environnement ?", @@ -182,31 +212,32 @@ "remove_shared_request": "Are you sure you want to permanently delete this shared request?", "remove_team": "Voulez-vous vraiment supprimer cette équipe ?", "remove_telemetry": "Êtes-vous sûr de vouloir désactiver la télémétrie ?", - "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", - "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Voulez-vous vraiment synchroniser cet espace de travail ?" + "request_change": "Êtes-vous sûr de vouloir rejeter la demande en cours ? Les modifications non enregistrées seront perdues.", + "save_unsaved_tab": "Souhaitez-vous enregistrer les modifications apportées dans cet onglet ?", + "sync": "Voulez-vous vraiment synchroniser cet espace de travail ?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { - "add_parameters": "Add to parameters", - "open_request_in_new_tab": "Open request in new tab", - "set_environment_variable": "Set as variable" + "add_parameters": "Ajouter aux paramètres", + "open_request_in_new_tab": "Ouvrir la demande dans un nouvel onglet", + "set_environment_variable": "Définir comme variable" }, "cookies": { "modal": { - "cookie_expires": "Expires", - "cookie_name": "Name", - "cookie_path": "Path", - "cookie_string": "Cookie string", - "cookie_value": "Value", - "empty_domain": "Domain is empty", - "empty_domains": "Domain list is empty", - "enter_cookie_string": "Enter cookie string", - "interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.", - "managed_tab": "Managed", - "new_domain_name": "New domain name", - "no_cookies_in_domain": "No cookies set for this domain", - "raw_tab": "Raw", - "set": "Set a cookie" + "cookie_expires": "Expiration", + "cookie_name": "Nom", + "cookie_path": "Chemin d'accès", + "cookie_string": "Chaîne de caractères de cookie", + "cookie_value": "Valeur", + "empty_domain": "Le domaine est vide", + "empty_domains": "La liste des domaines est vide", + "enter_cookie_string": "Saisir la chaîne de caractères du cookie", + "interceptor_no_support": "L'intercepteur que vous avez sélectionné ne prend pas en charge les cookies. Sélectionnez un autre intercepteur et réessayez.", + "managed_tab": "Gestion", + "new_domain_name": "Nouveau nom de domaine", + "no_cookies_in_domain": "Aucun cookie n'est défini pour ce domaine", + "raw_tab": "Brut", + "set": "Définir un cookie" } }, "count": { @@ -238,13 +269,16 @@ "pending_invites": "Il n'y a pas d'invitation en cours pour cette équipe", "profile": "Connectez-vous pour voir votre profil", "protocols": "Les protocoles sont vides", + "request_variables": "This request does not have any request variables", "schema": "Se connecter à un point de terminaison GraphQL", - "shared_requests": "Shared requests are empty", + "secret_environments": "Secrets are not synced to Hoppscotch", + "shared_requests": "Il n'y a pas de requêtes partagées", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Nom de l'équipe vide", "teams": "Les équipes sont vides", "tests": "Il n'y a pas de tests pour cette requête", + "access_tokens": "Access tokens are empty", "shortcodes": "Les shortcodes sont vides" }, "environment": { @@ -253,15 +287,15 @@ "create_new": "Créer un nouvel environnement", "created": "Environnement créé", "deleted": "Environnement supprimé", - "duplicated": "Environment duplicated", + "duplicated": "Environnement dupliqué", "edit": "Modifier l'environnement", "empty_variables": "No variables", "global": "Global", - "global_variables": "Global variables", - "import_or_create": "Import or create a environment", + "global_variables": "Variables globales", + "import_or_create": "Importer ou créer un environnement", "invalid_name": "Veuillez fournir un nom valide pour l'environnement", - "list": "Environment variables", - "my_environments": "My Environments", + "list": "Variables d'environnement", + "my_environments": "Mes environnements", "name": "Name", "nested_overflow": "les variables d'environnement imbriquées sont limitées à 10 niveaux", "new": "Nouvel environnement", @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Sélectionnez l'environnement", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,17 +315,21 @@ "updated": "Mise à jour de l'environnement", "value": "Value", "variable": "Variable", - "variable_list": "Liste des variables" + "variables": "Variables", + "variable_list": "Liste des variables", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", "browser_support_sse": "Ce navigateur ne semble pas prendre en charge les événements envoyés par le serveur.", "check_console_details": "Consultez le journal de la console pour plus de détails.", - "check_how_to_add_origin": "Check how you can add an origin", + "check_how_to_add_origin": "Vérifiez comment vous pouvez ajouter une origine", "curl_invalid_format": "cURL n'est pas formaté correctement", - "danger_zone": "Danger zone", - "delete_account": "Your account is currently an owner in these teams:", - "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "danger_zone": "Zone de danger", + "delete_account": "Votre compte est actuellement propriétaire de ces équipes :", + "delete_account_description": "Vous devez vous retirer, transférer la propriété ou supprimer ces équipes avant de pouvoir supprimer votre compte.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Nom de la requête vide", "f12_details": "(F12 pour les détails)", "gql_prettify_invalid_query": "Impossible de formater une requête non valide, résolvez les erreurs de syntaxe de la requête et réessayer", @@ -297,33 +337,42 @@ "incorrect_email": "Email incorrect", "invalid_link": "Lien invalide", "invalid_link_description": "Le lien sur lequel vous avez cliqué n'est pas valide ou a expiré.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "JSON non valide", "json_prettify_invalid_body": "Impossible de formater un corps non valide, résolvez les erreurs de syntaxe json et réessayez", "network_error": "Il semble y avoir une erreur de réseau. Veuillez réessayer.", "network_fail": "Impossible d'envoyer la requête", - "no_collections_to_export": "No collections to export. Please create a collection to get started.", + "no_collections_to_export": "Aucune collection à exporter. Veuillez créer une collection pour commencer.", "no_duration": "Pas de durée", - "no_environments_to_export": "No environments to export. Please create an environment to get started.", + "no_environments_to_export": "Aucun environnement à exporter. Veuillez créer un environnement pour commencer.", "no_results_found": "Aucune correspondance trouvée", "page_not_found": "Cette page n'a pas pu être trouvée", - "please_install_extension": "Please install the extension and add origin to the extension.", - "proxy_error": "Proxy error", + "please_install_extension": "Veuillez installer l'extension et ajouter l'origine à l'extension.", + "proxy_error": "Erreur de proxy", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Impossible d'exécuter le script de pré-requête", "something_went_wrong": "Quelque chose s'est mal passé", - "test_script_fail": "Impossible d'exécuter le script post-requête" + "test_script_fail": "Impossible d'exécuter le script post-requête", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Exporter au format JSON", "create_secret_gist": "Créer un Gist secret", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist créé", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Connectez-vous avec GitHub pour créer un Gist secret", - "title": "Exportation" + "title": "Exportation", + "success": "Successfully exported", + "gist_created": "Gist créé" }, "filter": { - "all": "All", - "none": "None", - "starred": "Starred" + "all": "Tout", + "none": "Aucun", + "starred": "Étoilé" }, "folder": { "created": "Dossier créé", @@ -334,19 +383,20 @@ "renamed": "Dossier renommé" }, "graphql": { - "connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?", - "connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is", - "connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is", + "connection_switch_confirm": "Voulez-vous vous connecter avec le dernier point de terminaison GraphQL ?", + "connection_switch_new_url": "Le passage à un autre onglet vous déconnectera de la connexion GraphQL active. La nouvelle URL de connexion est", + "connection_switch_url": "Vous êtes connecté à un point de terminaison GraphQL dont l'URL de connexion est", "mutations": "Mutations", "schema": "Schéma", "subscriptions": "Abonnements", - "switch_connection": "Switch connection" + "switch_connection": "Changer de connexion", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" }, "group": { - "time": "Time", + "time": "Temps", "url": "URL" }, "header": { @@ -377,7 +427,7 @@ "import": { "collections": "Importer des collections", "curl": "Importer en cURL", - "environments_from_gist": "Import From Gist", + "environments_from_gist": "Importer depuis Gist", "environments_from_gist_description": "Import Hoppscotch Environments From Gist", "failed": "Échec de l'importation", "from_file": "Import from File", @@ -406,30 +456,35 @@ "json_description": "Importer des collections depuis un fichier JSON Hoppscotch", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Importer" + "title": "Importer", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { - "description": "Inspect possible errors", + "description": "Inspecter les erreurs possibles", "environment": { - "add_environment": "Add to Environment", - "not_found": "Environment variable “{environment}” not found." + "add_environment": "Ajouter à l'environnement", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", + "not_found": "La variable d'environnement “{environment}“ n'a pas été trouvée." }, "header": { - "cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." + "cookie": "Le navigateur ne permet pas à Hoppscotch de définir l'en-tête Cookie. Pendant que nous travaillons sur l'application de bureau Hoppscotch (bientôt disponible), veuillez utiliser l'en-tête d'autorisation à la place." }, "response": { - "401_error": "Please check your authentication credentials.", - "404_error": "Please check your request URL and method type.", - "cors_error": "Please check your Cross-Origin Resource Sharing configuration.", - "default_error": "Please check your request.", - "network_error": "Please check your network connection." + "401_error": "Veuillez vérifier vos informations d'authentification.", + "404_error": "Veuillez vérifier l'URL de votre demande et le type de méthode.", + "cors_error": "Veuillez vérifier la configuration du partage des ressources entre les origines.", + "default_error": "Veuillez vérifier votre demande.", + "network_error": "Veuillez vérifier votre connexion réseau." }, - "title": "Inspector", + "title": "Inspecteur", "url": { - "extension_not_installed": "Extension not installed.", - "extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", - "extention_enable_action": "Enable Browser Extension", - "extention_not_enabled": "Extension not enabled." + "extension_not_installed": "L'extension n'est pas installée.", + "extension_unknown_origin": "Assurez-vous d'avoir ajouté l'origine du point de terminaison de l'API à la liste des extensions du navigateur Hoppscotch.", + "extention_enable_action": "Activer l'extension du navigateur", + "extention_not_enabled": "L'extension n'est pas activée." } }, "layout": { @@ -440,25 +495,25 @@ "row": "Disposition horizontale" }, "modal": { - "close_unsaved_tab": "You have unsaved changes", + "close_unsaved_tab": "Vous avez des modifications non enregistrées", "collections": "Collections", "confirm": "Confirmer", "customize_request": "Customize Request", "edit_request": "Modifier la requête", "import_export": "Importer / Exporter", - "share_request": "Share Request" + "share_request": "Partager une requête" }, "mqtt": { - "already_subscribed": "You are already subscribed to this topic.", - "clean_session": "Clean Session", - "clear_input": "Clear input", - "clear_input_on_send": "Clear input on send", + "already_subscribed": "Vous êtes déjà abonné à ce sujet.", + "clean_session": "Effacer la Session", + "clear_input": "Effacer la saisie", + "clear_input_on_send": "Effacer la saisie lors de l'envoi", "client_id": "Client ID", - "color": "Pick a color", + "color": "Choisir la couleur", "communication": "Communication", "connection_config": "Connection Config", - "connection_not_authorized": "This MQTT connection does not use any authentication.", - "invalid_topic": "Please provide a topic for the subscription", + "connection_not_authorized": "Cette connexion MQTT n'utilise pas d'authentification.", + "invalid_topic": "Veuillez fournir un sujet pour l'abonnement", "keep_alive": "Keep Alive", "log": "Infos", "lw_message": "Last-Will Message", @@ -467,7 +522,7 @@ "lw_topic": "Last-Will Topic", "message": "Message", "new": "New Subscription", - "not_connected": "Please start a MQTT connection first.", + "not_connected": "Veuillez d'abord établir une connexion MQTT.", "publish": "Publier", "qos": "QoS", "ssl": "SSL", @@ -481,7 +536,7 @@ "navigation": { "doc": "Documents", "graphql": "GraphQL", - "profile": "Profile", + "profile": "Profil", "realtime": "Temps réel", "rest": "REST", "settings": "Paramètres" @@ -494,7 +549,7 @@ }, "profile": { "app_settings": "Réglages de l'application", - "default_hopp_displayname": "Unnamed User", + "default_hopp_displayname": "Utilisateur anonyme", "editor": "Éditeur", "editor_description": "Les éditeurs peuvent ajouter, modifier et supprimer des demandes.", "email_verification_mail": "Un e-mail de vérification a été envoyé à votre adresse e-mail. Veuillez cliquer sur le lien pour vérifier votre adresse électronique.", @@ -527,7 +582,7 @@ "enter_curl": "Entrer cURL", "generate_code": "Générer le code", "generated_code": "Code généré", - "go_to_authorization_tab": "Go to Authorization tab", + "go_to_authorization_tab": "Aller à l'autorisation", "go_to_body_tab": "Go to Body tab", "header_list": "Liste des en-têtes", "invalid_name": "Veuillez fournir un nom pour la requête", @@ -535,9 +590,9 @@ "moved": "Request moved", "name": "Nom de la requête", "new": "Nouvelle requête", - "order_changed": "Request Order Updated", + "order_changed": "Demande de commande Mise à jour", "override": "Remplacer", - "override_help": "Set Content-Type in Headers", + "override_help": "Définir Content-Type dans les en-têtes", "overriden": "Remplacé", "parameter_list": "Paramètres de requête", "parameters": "Paramètres", @@ -545,8 +600,9 @@ "payload": "Charge utile", "query": "Requête", "raw_body": "Corps de requête brut", - "rename": "Rename Request", + "rename": "Demande de renommage", "renamed": "Requête renommée", + "request_variables": "Request variables", "run": "Lancer", "save": "Sauvegarder", "save_as": "Enregistrer sous", @@ -558,6 +614,7 @@ "title": "Requête", "type": "Type de requête", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Variables", "view_my_links": "Voir mes liens", "copy_link": "Copier le lien" @@ -565,7 +622,7 @@ "response": { "audio": "Audio", "body": "Corps de réponse", - "filter_response_body": "Filter JSON response body (uses JSONPath syntax)", + "filter_response_body": "Filtrer le corps de la réponse JSON (utilise la syntaxe JSONPath)", "headers": "En-têtes", "html": "HTML", "image": "Image", @@ -577,14 +634,14 @@ "status": "Statut", "time": "Temps", "title": "Réponse", - "video": "Video", + "video": "Vidéo", "waiting_for_connection": "En attente de connexion", "xml": "XML" }, "settings": { "accent_color": "Couleur d'accent", "account": "Compte", - "account_deleted": "Your account has been deleted", + "account_deleted": "Votre compte a été supprimé", "account_description": "Personnalisez les paramètres de votre compte.", "account_email_description": "Votre adresse e-mail principale.", "account_name_description": "Ceci est votre nom d'affichage.", @@ -593,8 +650,8 @@ "black_mode": "Noir", "choose_language": "Choisissez la langue", "dark_mode": "Sombre", - "delete_account": "Delete account", - "delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.", + "delete_account": "Supprimer le compte", + "delete_account_description": "Lorsque vous supprimez votre compte, toutes vos données sont définitivement effacées. Cette action ne peut être annulée.", "expand_navigation": "Expand navigation", "experiments": "Expériences", "experiments_notice": "Il s'agit d'une collection d'expériences sur lesquelles nous travaillons et qui pourraient s'avérer utiles, amusantes, les deux ou aucune. Ils ne sont pas définitifs et peuvent ne pas être stables, donc si quelque chose de trop étrange se produit, ne paniquez pas. Il suffit d'éteindre le truc. Blague à part,", @@ -602,7 +659,7 @@ "extension_version": "Version d'extension", "extensions": "Extensions", "extensions_use_toggle": "Utilisez l'extension de navigateur pour envoyer des requêtes (le cas échéant)", - "follow": "Follow Us", + "follow": "Suivez-nous", "interceptor": "Intercepteur", "interceptor_description": "Middleware entre l'application et les API.", "language": "Langue", @@ -632,7 +689,7 @@ "theme_description": "Personnalisez le thème de votre application.", "use_experimental_url_bar": "Utiliser la barre d'URL expérimentale avec mise en évidence de l'environnement", "user": "Utilisateur", - "verified_email": "Verified email", + "verified_email": "E-mail vérifié", "verify_email": "Vérifier l'email" }, "shared_requests": { @@ -685,20 +742,20 @@ "title": "Navigation" }, "others": { - "prettify": "Prettify Editor's Content", - "title": "Others" + "prettify": "Améliorer le contenu de l'éditeur", + "title": "Autres" }, "request": { "delete_method": "Sélectionnez la méthode DELETE", "get_method": "Sélectionnez la méthode GET", "head_method": "Sélectionnez la méthode HEAD", - "import_curl": "Import cURL", + "import_curl": "Importer cURL", "method": "Méthode", "next_method": "Sélectionnez la méthode suivante", "post_method": "Sélectionnez la méthode POST", "previous_method": "Sélectionnez la méthode précédente", "put_method": "Sélectionnez la méthode PUT", - "rename": "Rename Request", + "rename": "Demande de renommage", "reset_request": "Réinitialiser la requête", "save_request": "Save Request", "save_to_collections": "Enregistrer dans les collections", @@ -729,89 +786,96 @@ }, "socketio": { "communication": "Communication", - "connection_not_authorized": "This SocketIO connection does not use any authentication.", + "connection_not_authorized": "Cette connexion SocketIO n'utilise pas d'authentification.", "event_name": "Nom de l'événement", "events": "Événements", "log": "Infos", "url": "URL" }, "spotlight": { - "change_language": "Change Language", + "change_language": "Changer de langue", "environments": { - "delete": "Delete current environment", - "duplicate": "Duplicate current environment", - "duplicate_global": "Duplicate global environment", - "edit": "Edit current environment", - "edit_global": "Edit global environment", - "new": "Create new environment", - "new_variable": "Create a new environment variable", + "delete": "Supprimer l'environnement actuel", + "duplicate": "Dupliquer l'environnement actuel", + "duplicate_global": "Duplication de l'environnement global", + "edit": "Modifier l'environnement actuel", + "edit_global": "Modifier l'environnement mondial", + "new": "Créer un nouvel environnement", + "new_variable": "Créer une nouvelle variable d'environnement", "title": "Environments" }, "general": { - "chat": "Chat with support", - "help_menu": "Help and support", - "open_docs": "Read Documentation", - "open_github": "Open GitHub repository", - "open_keybindings": "Keyboard shortcuts", + "chat": "Chat avec le support", + "help_menu": "Aide et assistance", + "open_docs": "Lire la documentation", + "open_github": "Ouvrir le dépôt GitHub", + "open_keybindings": "Raccourcis clavier", "social": "Social", - "title": "General" + "title": "Général" }, "graphql": { - "connect": "Connect to server", - "disconnect": "Disconnect from server" + "connect": "Connexion au serveur", + "disconnect": "Déconnexion du serveur" }, "miscellaneous": { - "invite": "Invite your friends to Hoppscotch", - "title": "Miscellaneous" + "invite": "Invitez vos amis à Hoppscotch", + "title": "Divers" }, "request": { - "save_as_new": "Save as new request", - "select_method": "Select method", - "switch_to": "Switch to", - "tab_authorization": "Authorization tab", - "tab_body": "Body tab", - "tab_headers": "Headers tab", - "tab_parameters": "Parameters tab", - "tab_pre_request_script": "Pre-request script tab", - "tab_query": "Query tab", - "tab_tests": "Tests tab", - "tab_variables": "Variables tab" + "save_as_new": "Sauvegarder comme nouvelle demande", + "select_method": "Sélectionner la méthode", + "switch_to": "Basculer vers", + "tab_authorization": "Onglet Autorisation", + "tab_body": "Onglet du corps", + "tab_headers": "Onglet En-têtes", + "tab_parameters": "Onglet Paramètres", + "tab_pre_request_script": "Onglet script de pré-demande", + "tab_query": "Onglet Requête", + "tab_tests": "Onglet Tests", + "tab_variables": "Onglet Variables" }, "response": { - "copy": "Copy response", - "download": "Download response as file", - "title": "Response" + "copy": "Copier la réponse", + "download": "Télécharger la réponse sous forme de fichier", + "title": "Réponse" }, "section": { - "interceptor": "Interceptor", + "interceptor": "Intercepteur", "interface": "Interface", - "theme": "Theme", - "user": "User" + "theme": "Thème", + "user": "Utilisateur" }, "settings": { - "change_interceptor": "Change Interceptor", - "change_language": "Change Language", + "change_interceptor": "Changer d'intercepteur", + "change_language": "Changer de langue", "theme": { - "black": "Black", - "dark": "Dark", - "light": "Light", - "system": "System preference" + "black": "Noir", + "dark": "Sombre", + "light": "Clair", + "system": "Préférence du système" } }, "tab": { - "close_current": "Close current tab", - "close_others": "Close all other tabs", - "duplicate": "Duplicate current tab", - "new_tab": "Open a new tab", - "title": "Tabs" + "close_current": "Fermer l'onglet actuel", + "close_others": "Fermer tous les autres onglets", + "duplicate": "Dupliquer l'onglet actuel", + "new_tab": "Ouvrir un nouvel onglet", + "title": "Onglets" }, "workspace": { - "delete": "Delete current team", - "edit": "Edit current team", - "invite": "Invite people to team", - "new": "Create new team", - "switch_to_personal": "Switch to your personal workspace", - "title": "Teams" + "delete": "Supprimer l'équipe actuelle", + "edit": "Modifier l'équipe actuelle", + "invite": "Inviter les gens à rejoindre l'équipe", + "new": "Créer une nouvelle équipe", + "switch_to_personal": "Passez à votre espace de travail personnel", + "title": "Les équipes" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -837,12 +901,12 @@ "disconnected": "Déconnecté", "disconnected_from": "Déconnecté de {name}", "docs_generated": "Documentation générée", - "download_failed": "Download failed", + "download_failed": "Téléchargement échoué", "download_started": "Téléchargement commencé", "enabled": "Active", "file_imported": "Fichier importé", "finished_in": "Terminé en {duration} ms", - "hide": "Hide", + "hide": "Cacher", "history_deleted": "Historique supprimé", "linewrap": "Retour à la ligne", "loading": "Chargement...", @@ -853,7 +917,7 @@ "published_error": "Quelque chose s'est mal passé lors de la publication du message : {topic} dans le sujet : {message}", "published_message": "Message publié : {message} au sujet : {topic}", "reconnection_error": "Échec de la reconnexion", - "show": "Show", + "show": "Afficher", "subscribed_failed": "Échec de l'inscription au sujet : {topic}", "subscribed_success": "Inscription réussie au sujet : {topic}", "unsubscribed_failed": "Échec de la désinscription du sujet : {topic}", @@ -862,34 +926,37 @@ }, "support": { "changelog": "En savoir plus sur les dernières versions", - "chat": "Des questions? Discutez avec nous!", + "chat": "Des questions ? Discutez avec nous!", "community": "Posez des questions et aidez les autres", "documentation": "En savoir plus sur Hoppscotch", "forum": "Posez des questions et obtenez des réponses", "github": "Follow us on Github", "shortcuts": "Parcourir l'application plus rapidement", - "team": "Entrez en contact avec l'équipe", "title": "Support", - "twitter": "Suivez-nous sur Twitter" + "twitter": "Suivez-nous sur Twitter", + "team": "Entrez en contact avec l'équipe" }, "tab": { "authorization": "Autorisation", "body": "Corps", - "close": "Close Tab", - "close_others": "Close other Tabs", + "close": "Fermer l'onglet", + "close_others": "Fermer les autres onglets", "collections": "Collections", "documentation": "Documentation", - "duplicate": "Duplicate Tab", + "duplicate": "Dupliquer l'onglet", "environments": "Environments", "headers": "En-têtes", - "history": "Histoire", + "history": "Historique", "mqtt": "MQTT", "parameters": "Paramètres", "pre_request_script": "Script de pré-requête", "queries": "Requêtes", "query": "Requête", "schema": "Schema", - "shared_requests": "Shared Requests", + "shared_requests": "Requêtes partagées", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "ESS", "tests": "Tests", @@ -906,7 +973,7 @@ "email_do_not_match": "L'email ne correspond pas aux détails de votre compte. Contactez le propriétaire de votre équipe.", "exit": "Quitter l'équipe", "exit_disabled": "Seul le propriétaire ne peut pas quitter l'équipe", - "failed_invites": "Failed invites", + "failed_invites": "Échec des invitations", "invalid_coll_id": "Invalid collection ID", "invalid_email_format": "Le format de l'e-mail n'est pas valide", "invalid_id": "L'email ne correspond pas aux détails de votre compte. Contactez le propriétaire de votre équipe.", @@ -918,7 +985,6 @@ "invite_tooltip": "Invitez des personnes à cet espace de travail", "invited_to_team": "{owner} vous invite à rejoindre {team}", "join": "Invitation accepté", - "join_beta": "Rejoignez le programme bêta pour accéder aux équipes.", "join_team": "Rejoindre {team}", "joined_team": "Vous avez rejoint {team}", "joined_team_description": "Vous êtes maintenant membre de cette équipe", @@ -942,21 +1008,26 @@ "no_request_found": "Request not found.", "not_found": "Équipe non trouvée. Contactez le propriétaire de votre équipe.", "not_valid_viewer": "Vous n'êtes pas un visionneur valide. Contactez le propriétaire de votre équipe.", - "parent_coll_move": "Cannot move collection to a child collection", + "parent_coll_move": "Impossible de déplacer une collection vers une collection enfant", "pending_invites": "Invitations en attente", "permissions": "Autorisations", - "same_target_destination": "Same target and destination", + "same_target_destination": "Même destinataire et même cible", "saved": "Équipe enregistrée", "select_a_team": "Choisir une équipe", - "success_invites": "Success invites", + "success_invites": "Les invitations réussites", "title": "Équipes", "we_sent_invite_link": "Nous avons envoyé un lien d'invitation à tous les invités !", - "we_sent_invite_link_description": "Demandez à tous les invités de vérifier leur boîte de réception. Cliquez sur le lien pour rejoindre l'équipe." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Demandez à tous les invités de vérifier leur boîte de réception. Cliquez sur le lien pour rejoindre l'équipe.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Rejoignez le programme bêta pour accéder aux équipes." }, "team_environment": { - "deleted": "Environment Deleted", - "duplicate": "Environment Duplicated", - "not_found": "Environment not found." + "deleted": "Environment supprimé", + "duplicate": "Environment dupliqué", + "not_found": "Environment non trouvé" }, "test": { "failed": "Test échoué", @@ -976,10 +1047,51 @@ "url": "URL" }, "workspace": { - "change": "Change workspace", - "personal": "My Workspace", - "team": "Team Workspace", - "title": "Workspaces" + "change": "Changer d'espace de travail", + "personal": "Mon espace de travail", + "other_workspaces": "My Workspaces", + "team": "Espace de travail de l'équipe", + "title": "Espaces de travail" + }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" }, "shortcodes": { "actions": "Actions", diff --git a/packages/hoppscotch-common/locales/he.json b/packages/hoppscotch-common/locales/he.json index 327fb5ba16..a70ed02307 100644 --- a/packages/hoppscotch-common/locales/he.json +++ b/packages/hoppscotch-common/locales/he.json @@ -24,8 +24,10 @@ "go_back": "תחזור", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "תווית", "learn_more": "למד עוד", + "download_here": "Download here", "less": "Less", "more": "יותר", "new": "חָדָשׁ", @@ -43,6 +45,7 @@ "search": "לחפש", "send": "לִשְׁלוֹחַ", "share": "Share", + "show_secret": "Show secret", "start": "הַתחָלָה", "starting": "Starting", "stop": "תפסיק", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "הקלד פקודה או חפש ...", "we_use_cookies": "אנו משתמשים בעוגיות", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "מה חדש?", + "see_whats_new": "See what’s new", "wiki": "ויקי" }, "auth": { "account_exists": "החשבון קיים עם אישורים שונים - התחבר לקישור שני החשבונות", "all_sign_in_options": "כל אפשרויות הכניסה", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "המשך עם מייל", "continue_with_github": "המשך עם GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "המשך עם גוגל", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "המשך עם OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "סיסמה", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "אֲסִימוֹן", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "ערוך אוסף", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "אנא ספק שם תקף לאוסף", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "בחר אוסף", "select_location": "תבחר מיקום", + "details": "Details", "select_team": "בחר צוות", "team_collections": "אוספי צוות" }, @@ -184,7 +214,8 @@ "remove_telemetry": "האם אתה בטוח שברצונך לבטל את הסכמתך לטלמטריה?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "האם אתה בטוח שברצונך לסנכרן את סביבת העבודה הזו?" + "sync": "האם אתה בטוח שברצונך לסנכרן את סביבת העבודה הזו?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "There are no pending invites for this team", "profile": "Login to view your profile", "protocols": "הפרוטוקולים ריקים", + "request_variables": "This request does not have any request variables", "schema": "התחבר לנקודת קצה של GraphQL", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "שם הקבוצה ריק", "teams": "הקבוצות ריקות", "tests": "אין בדיקות לבקשה זו", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "בחר סביבה", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment updation", "value": "Value", "variable": "Variable", - "variable_list": "רשימת משתנים" + "variables": "Variables", + "variable_list": "רשימת משתנים", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "שם הבקשה ריק", "f12_details": "(F12 לפרטים)", "gql_prettify_invalid_query": "לא ניתן היה לייפות שאילתה לא חוקית, לפתור שגיאות תחביר שאילתות ולנסות שוב", @@ -297,6 +337,7 @@ "incorrect_email": "Incorrect email", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "לא ניתן היה לייפות גוף לא חוקי, לפתור שגיאות תחביר של json ולנסות שוב", "network_error": "There seems to be a network error. Please try again.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "לא ניתן להפעיל סקריפט של בקשה מראש", "something_went_wrong": "משהו השתבש", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "ייצא כ- JSON", "create_secret_gist": "צור גיסט סודי", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "גיסט נוצר", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "התחבר עם GitHub כדי ליצור תמצית סודית", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "גיסט נוצר" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "מוטציות", "schema": "סכֵימָה", "subscriptions": "מנויים", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "יְבוּא" + "title": "יְבוּא", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "גוף בקשה גולמית", "rename": "Rename Request", "renamed": "שם הבקשה שונה", + "request_variables": "Request variables", "run": "לָרוּץ", "save": "להציל", "save_as": "שמור כ", @@ -558,6 +614,7 @@ "title": "בַּקָשָׁה", "type": "סוג בקשה", "url": "כתובת URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "משתנים", "view_my_links": "View my links", "copy_link": "העתק קישור" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "שאל שאלות וקבל תשובות", "github": "Follow us on Github", "shortcuts": "עיון באפליקציה מהר יותר", - "team": "צרו קשר עם הצוות", "title": "תמיכה", - "twitter": "עקבו אחרינו בטוויטר" + "twitter": "עקבו אחרינו בטוויטר", + "team": "צרו קשר עם הצוות" }, "tab": { "authorization": "הרשאה", @@ -890,6 +954,9 @@ "query": "שאילתא", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "בדיקות", @@ -918,7 +985,6 @@ "invite_tooltip": "Invite people to this workspace", "invited_to_team": "{owner} invited you to join {team}", "join": "Invitation accepted", - "join_beta": "הצטרף לתוכנית הביטא לגישה לצוותים.", "join_team": "Join {team}", "joined_team": "You have joined {team}", "joined_team_description": "You are now a member of this team", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "צוותים", "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "הצטרף לתוכנית הביטא לגישה לצוותים." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/hu.json b/packages/hoppscotch-common/locales/hu.json index 1a9b925fec..15dafc1341 100644 --- a/packages/hoppscotch-common/locales/hu.json +++ b/packages/hoppscotch-common/locales/hu.json @@ -1,17 +1,17 @@ { "action": { - "add": "Add", + "add": "Hozzáadás", "autoscroll": "Automatikus görgetés", "cancel": "Mégse", "choose_file": "Válasszon egy fájlt", "clear": "Törlés", "clear_all": "Összes törlése", - "clear_history": "Clear all History", + "clear_history": "Összes előzmény törlése", "close": "Bezárás", "connect": "Kapcsolódás", "connecting": "Kapcsolódás", "copy": "Másolás", - "create": "Create", + "create": "Létrehozás", "delete": "Törlés", "disconnect": "Leválasztás", "dismiss": "Eltüntetés", @@ -24,8 +24,10 @@ "go_back": "Vissza", "go_forward": "Előre", "group_by": "Csoportosítás", + "hide_secret": "Hide secret", "label": "Címke", "learn_more": "Tudjon meg többet", + "download_here": "Download here", "less": "Kevesebb", "more": "Több", "new": "Új", @@ -33,16 +35,17 @@ "open_workspace": "Munkaterület megnyitása", "paste": "Beillesztés", "prettify": "Csinosítás", - "properties": "Properties", + "properties": "Tulajdonságok", "remove": "Eltávolítás", - "rename": "Rename", + "rename": "Átnevezés", "restore": "Visszaállítás", "save": "Mentés", "scroll_to_bottom": "Görgetés az aljára", "scroll_to_top": "Görgetés a tetejére", "search": "Keresés", "send": "Küldés", - "share": "Share", + "share": "Megosztás", + "show_secret": "Show secret", "start": "Indítás", "starting": "Indítás", "stop": "Leállítás", @@ -61,9 +64,9 @@ "app": { "chat_with_us": "Csevegjen velünk", "contact_us": "Lépjen kapcsolatba velünk", - "cookies": "Cookies", + "cookies": "Sütik", "copy": "Másolás", - "copy_interface_type": "Copy interface type", + "copy_interface_type": "Interface típusának másolása", "copy_user_id": "Felhasználó-hitelesítési token másolása", "developer_option": "Fejlesztői beállítások", "developer_option_description": "Fejlesztői eszközök, amelyek segítenek a Hoppscotch fejlesztésében és karbantartásában.", @@ -79,15 +82,15 @@ "keyboard_shortcuts": "Gyorsbillentyűk", "name": "Hoppscotch", "new_version_found": "Új verzió található. Töltse újra az oldalt a frissítéshez.", - "open_in_hoppscotch": "Open in Hoppscotch", + "open_in_hoppscotch": "Megnyitás Hoppscotch-ban.", "options": "Beállítások", "proxy_privacy_policy": "Proxy adatvédelmi irányelvei", "reload": "Újratöltés", "search": "Keresés", "share": "Megosztás", "shortcuts": "Gyorsbillentyűk", - "social_description": "Follow us on social media to stay updated with the latest news, updates and releases.", - "social_links": "Social links", + "social_description": "Kövess minket a közösségi médiában, hogy ne maradj le a hírekről, frissítésekről és új kiadásokról.", + "social_links": "Közösségi média linkek", "spotlight": "Reflektorfény", "status": "Állapot", "status_description": "A weboldal állapotának ellenőrzése", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Írjon be parancsot vagy keresést…", "we_use_cookies": "Sütiket használunk", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Mik az újdonságok?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "A fiók különböző hitelesítési adatokkal létezik – jelentkezzen be a két fiók összekapcsolásához", "all_sign_in_options": "Összes bejelentkezési lehetőség", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Folytatás e-mail-címmel", "continue_with_github": "Folytatás GitHub használatával", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Folytatás Google használatával", "continue_with_microsoft": "Folytatás Microsoft használatával", "continue_with_oidc": "Folytatás OIDC használatával", @@ -120,27 +127,48 @@ }, "authorization": { "generate_token": "Token előállítása", - "graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", + "graphql_headers": "Azonosító fejléc connection_init tartalmaként elküldve", "include_in_url": "Felvétel az URL-be", - "inherited_from": "Inherited from {auth} from Parent Collection {collection} ", + "inherited_from": "Örökölt a(z) {auth}-tól, a(z) {collection} gyűjteményből ", "learn": "Tudja meg, hogyan", "oauth": { - "redirect_auth_server_returned_error": "Auth Server returned an error state", - "redirect_auth_token_request_failed": "Request to get the auth token failed", - "redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token", - "redirect_invalid_state": "Invalid State value present in the redirect", - "redirect_no_auth_code": "No Authorization Code present in the redirect", - "redirect_no_client_id": "No Client ID defined", - "redirect_no_client_secret": "No Client Secret Defined", - "redirect_no_code_verifier": "No Code Verifier Defined", - "redirect_no_token_endpoint": "No Token Endpoint Defined", - "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", - "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "redirect_auth_server_returned_error": "Az Auth szerver hibás állapottal tért vissza", + "redirect_auth_token_request_failed": "Kérés az auth token lekéréséhez sikertelen", + "redirect_auth_token_request_invalid_response": "Érvénytelen válasz a Token Endpoint-tól, az auth token lekérésekpr", + "redirect_invalid_state": "Érvénytelen állapotérték az átirányításban", + "redirect_no_auth_code": "Nincs azonosítás az átirányításban", + "redirect_no_client_id": "Nincs felhasználó azonosító", + "redirect_no_client_secret": "Nincs felhasználó jelszó", + "redirect_no_code_verifier": "Nincs kódellenőrző", + "redirect_no_token_endpoint": "Nincs \"Token Endpoint\"", + "something_went_wrong_on_oauth_redirect": "Valami rosszul sikerült az OAuth átirányításakor", + "something_went_wrong_on_token_generation": "Valami rosszul sikerült a token generálásakor", + "token_generation_oidc_discovery_failed": "Hiba a token generálásakor: OpenID Connect Discovery hiba", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Átadta", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Jelszó", - "save_to_inherit": "Please save this request in any collection to inherit the authorization", + "save_to_inherit": "Kérjük, mentse el ezt kérést bármelyik gyűjteménybe, hogy az azonosítás örökölhető lehessen", "token": "Token", "type": "Felhatalmazás típusa", "username": "Felhasználónév" @@ -149,7 +177,8 @@ "created": "Gyűjtemény létrehozva", "different_parent": "Nem lehet átrendezni a különböző szülővel rendelkező gyűjteményt", "edit": "Gyűjtemény szerkesztése", - "import_or_create": "Import or create a collection", + "import_or_create": "Gyűjtemény importálása vagy létrehozása", + "import_collection": "Import Collection", "invalid_name": "Adjon nevet a gyűjteménynek", "invalid_root_move": "A gyűjtemény már a gyökérben van", "moved": "Sikeresen áthelyezve", @@ -158,20 +187,21 @@ "name_length_insufficient": "A gyűjtemény nevének legalább 3 karakter hosszúságúnak kell lennie", "new": "Új gyűjtemény", "order_changed": "Gyűjtemény sorrendje frissítve", - "properties": "Collection Properties", - "properties_updated": "Collection Properties Updated", + "properties": "Gyűjtemény tulajdonságok", + "properties_updated": "Gyűjtemény tulajdonságai frissítve", "renamed": "Gyűjtemény átnevezve", "request_in_use": "A kérés használatban", "save_as": "Mentés másként", - "save_to_collection": "Save to Collection", + "save_to_collection": "Mentés egy gyűjteménybe", "select": "Gyűjtemény kiválasztása", "select_location": "Hely kiválasztása", + "details": "Details", "select_team": "Csapat kiválasztása", "team_collections": "Csapat gyűjteményei" }, "confirm": { - "close_unsaved_tab": "Are you sure you want to close this tab?", - "close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", + "close_unsaved_tab": "Biztos, hogy bezárja ezt a lapot?", + "close_unsaved_tabs": "Biztos, hogy bezárja az összes lapot? {count} elmentetlen lap el fog veszni.", "exit_team": "Biztosan el szeretné hagyni ezt a csapatot?", "logout": "Biztosan ki szeretne jelentkezni?", "remove_collection": "Biztosan véglegesen törölni szeretné ezt a gyűjteményt?", @@ -179,34 +209,35 @@ "remove_folder": "Biztosan véglegesen törölni szeretné ezt a mappát?", "remove_history": "Biztosan véglegesen törölni szeretné az összes előzményt?", "remove_request": "Biztosan véglegesen törölni szeretné ezt a kérést?", - "remove_shared_request": "Are you sure you want to permanently delete this shared request?", + "remove_shared_request": "Biztos, hogy véglegesen törölni szeretné ezt a megosztott kérést?", "remove_team": "Biztosan törölni szeretné ezt a csapatot?", "remove_telemetry": "Biztosan ki szeretné kapcsolni a telemetriát?", "request_change": "Biztosan el szeretné vetni a jelenlegi kérést? Minden mentetlen változtatás el fog veszni.", "save_unsaved_tab": "Szeretné menteni az ezen a lapon elvégzett változtatásokat?", - "sync": "Szeretné visszaállítani a munkaterületét a felhőből? Ez el fogja vetni a helyi folyamatát." + "sync": "Szeretné visszaállítani a munkaterületét a felhőből? Ez el fogja vetni a helyi folyamatát.", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { - "add_parameters": "Add to parameters", - "open_request_in_new_tab": "Open request in new tab", - "set_environment_variable": "Set as variable" + "add_parameters": "Paraméterek hozzáadása", + "open_request_in_new_tab": "Kérés megnyitása új lapot", + "set_environment_variable": "Változóként való beállítás" }, "cookies": { "modal": { - "cookie_expires": "Expires", - "cookie_name": "Name", - "cookie_path": "Path", - "cookie_string": "Cookie string", - "cookie_value": "Value", - "empty_domain": "Domain is empty", - "empty_domains": "Domain list is empty", - "enter_cookie_string": "Enter cookie string", - "interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.", - "managed_tab": "Managed", - "new_domain_name": "New domain name", - "no_cookies_in_domain": "No cookies set for this domain", - "raw_tab": "Raw", - "set": "Set a cookie" + "cookie_expires": "Lejárat", + "cookie_name": "Név", + "cookie_path": "Útvonal", + "cookie_string": "Süti szöveg", + "cookie_value": "Érték", + "empty_domain": "Üres domain", + "empty_domains": "Domain lista üres", + "enter_cookie_string": "Süti szövegének megadása", + "interceptor_no_support": "A kiválasztott interceptor nem támogatja a sütiket. Válasszon ki egy másik interceptor-t és próbálja újra.", + "managed_tab": "Menedzselt", + "new_domain_name": "Új domain neve", + "no_cookies_in_domain": "Nincs süti beállítva ehhez a domainhez.", + "raw_tab": "Nyers", + "set": "Süti beállítása" } }, "count": { @@ -222,7 +253,7 @@ "generate_message": "Importáljon bármilyen Hoppscotch-gyűjteményt, hogy API-dokumentációt készítsen a folyamat során." }, "empty": { - "authorization": "Ez a kérés nem használ felhatalmazást", + "authorization": "Ez a kérés nem használ azonosítást", "body": "Ennek a kérésnek nincs törzse", "collection": "A gyűjtemény üres", "collections": "A gyűjtemények üresek", @@ -238,13 +269,16 @@ "pending_invites": "Nincsenek függőben lévő meghívások ennél a csapatnál", "profile": "Jelentkezzen be a profilja megtekintéséhez", "protocols": "A protokollok üresek", + "request_variables": "This request does not have any request variables", "schema": "Kapcsolódjon egy GraphQL-végponthoz a séma megtekintéséhez", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "A feliratkozások üresek", "team_name": "A csapat neve üres", "teams": "Ön nem tartozik semmilyen csapathoz", "tests": "Nincsenek tesztek ehhez a kéréshez", + "access_tokens": "Access tokens are empty", "shortcodes": "A rövid kódok üresek" }, "environment": { @@ -253,43 +287,49 @@ "create_new": "Új környezet létrehozása", "created": "Környezet létrehozva", "deleted": "Környezet törlése", - "duplicated": "Environment duplicated", + "duplicated": "Környezet duplikálása", "edit": "Környezet szerkesztése", - "empty_variables": "No variables", - "global": "Global", - "global_variables": "Global variables", - "import_or_create": "Import or create a environment", + "empty_variables": "Nincs változó", + "global": "Globális", + "global_variables": "Globális változók", + "import_or_create": "Környezet importálása vagy létrehozása", "invalid_name": "Adjon nevet a környezetnek", - "list": "Environment variables", + "list": "Környezeti változók", "my_environments": "Saját környezetek", - "name": "Name", + "name": "Név", "nested_overflow": "az egymásba ágyazott környezeti változók 10 szintre vannak korlátozva", "new": "Új környezet", - "no_active_environment": "No active environment", + "no_active_environment": "Nincs aktív környezet", "no_environment": "Nincs környezet", "no_environment_description": "Nem lettek környezetek kiválasztva. Válassza ki, hogy mit kell tenni a következő változókkal.", - "quick_peek": "Environment Quick Peek", - "replace_with_variable": "Replace with variable", - "scope": "Scope", + "quick_peek": "Környezet gyors megnézése", + "replace_with_variable": "Cserélje le egy változóra", + "scope": "Hatókör", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Környezet kiválasztása", - "set": "Set environment", - "set_as_environment": "Set as environment", + "set": "Környezet beállítása", + "set_as_environment": "Környezetként való beállítás", "team_environments": "Csapatkörnyezetek", "title": "Környezetek", "updated": "Környezet frissítve", - "value": "Value", - "variable": "Variable", - "variable_list": "Változólista" + "value": "Érték", + "variable": "Változó", + "variables": "Variables", + "variable_list": "Változólista", + "properties": "Environment Properties", + "details": "Details" }, "error": { - "authproviders_load_error": "Unable to load auth providers", + "authproviders_load_error": "Nem sikerült betölteni az azonosító szolgáltatókat", "browser_support_sse": "Úgy tűnik, hogy ez a böngésző nem támogatja a kiszolgáló által küldött eseményeket.", "check_console_details": "Nézze meg a konzolnaplót a részletekért.", - "check_how_to_add_origin": "Check how you can add an origin", + "check_how_to_add_origin": "Ellenőrizze, hogy hogyan adhat hozzá forrást", "curl_invalid_format": "A cURL nincs megfelelően formázva", "danger_zone": "Veszélyes zóna", "delete_account": "Az Ön fiókja jelenleg tulajdonos ezekben a csapatokban:", "delete_account_description": "El kell távolítani magát, át kell adnia a tulajdonjogot vagy törölnie kell ezeket a csapatokat, mielőtt törölhetné a fiókját.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Üres kérésnév", "f12_details": "(F12 a részletekért)", "gql_prettify_invalid_query": "Nem sikerült csinosítani egy érvénytelen lekérdezést, oldja meg a lekérdezés szintaktikai hibáit, és próbálja újra", @@ -297,28 +337,37 @@ "incorrect_email": "Hibás e-mail", "invalid_link": "Érvénytelen hivatkozás", "invalid_link_description": "A kattintott hivatkozás érvénytelen vagy lejárt.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Érvénytelen JSON", "json_prettify_invalid_body": "Nem sikerült csinosítani egy érvénytelen törzset, oldja meg a JSON szintaktikai hibáit, és próbálja újra", "network_error": "Úgy tűnik, hogy hálózati hiba van. Próbálja újra.", "network_fail": "Nem sikerült elküldeni a kérést", - "no_collections_to_export": "No collections to export. Please create a collection to get started.", + "no_collections_to_export": "Nincs exportálható gyűjtemény. Kérjük, hozzon létre egyet, hogy elkezdhesse.", "no_duration": "Nincs időtartam", - "no_environments_to_export": "No environments to export. Please create an environment to get started.", + "no_environments_to_export": "Nincs exportálható környezet. Kérjük, hozzon létre egyet, hogy elkezdhesse.", "no_results_found": "Nincs találat", "page_not_found": "Ez az oldal nem található", - "please_install_extension": "Please install the extension and add origin to the extension.", - "proxy_error": "Proxy error", + "please_install_extension": "Kérjük telepítse a bővítményt és adja hozzá a forráshoz.", + "proxy_error": "Proxy hiba", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Nem sikerült végrehajtani a kérés előtti parancsfájlt", "something_went_wrong": "Valami elromlott", - "test_script_fail": "Nem sikerült végrehajtani a kérés utáni parancsfájlt" + "test_script_fail": "Nem sikerült végrehajtani a kérés utáni parancsfájlt", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Exportálás JSON formátumban", "create_secret_gist": "Titkos Gist létrehozása", - "failed": "Something went wrong while exporting", - "gist_created": "Gist létrehozva", + "create_secret_gist_tooltip_text": "Export as secret Gist", + "failed": "Valami hiba történt az exportálás közben", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Jelentkezzen be GitHub használatával a titkos Gist létrehozásához", - "title": "Exportálás" + "title": "Exportálás", + "success": "Successfully exported", + "gist_created": "Gist létrehozva" }, "filter": { "all": "Összes", @@ -334,16 +383,17 @@ "renamed": "Mappa átnevezve" }, "graphql": { - "connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?", - "connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is", - "connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is", + "connection_switch_confirm": "Szeretne csatlakozni a legújabb GraphQL végponttal?", + "connection_switch_new_url": "A tab váltása lecsatlakoztatta az aktív GraphQL kapcsolatról. Az új kapcsolat", + "connection_switch_url": "Kapcsolódott a GraphQL végponthoz. A kapcsolat", "mutations": "Mutációk", "schema": "Séma", "subscriptions": "Feliratkozások", - "switch_connection": "Switch connection" + "switch_connection": "Kapcsolat váltása", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { - "title": "GraphQL Collections" + "title": "GraphQL gyűjtemény" }, "group": { "time": "Idő", @@ -355,9 +405,9 @@ "save_workspace": "Saját munkaterület mentése" }, "helpers": { - "authorization": "A felhatalmazási fejléc automatikusan elő lesz állítva a kérés elküldésekor.", - "collection_properties_authorization": " This authorization will be set for every request in this collection.", - "collection_properties_header": "This header will be set for every request in this collection.", + "authorization": "Az Azonosítás fejléc automatikusan elő lesz állítva a kérés elküldésekor.", + "collection_properties_authorization": " Ez az azonosítás be lesz állítva minden kéréshez ebben a gyűjteményben.", + "collection_properties_header": "Ez a fejléc be lesz állítva mint minden kéréshez ebben a gyűjteményben.", "generate_documentation_first": "Először állítsa elő a dokumentációt", "network_fail": "Nem lehet elérni az API-végpontot. Ellenőrizze a hálózati kapcsolatot vagy válasszon egy másik elfogót, és próbálja újra.", "offline": "Úgy tűnik, hogy kapcsolat nélküli módban van. Előfordulhat, hogy a munkaterületen lévő adatok nem naprakészek.", @@ -377,11 +427,11 @@ "import": { "collections": "Gyűjtemények importálása", "curl": "cURL importálása", - "environments_from_gist": "Import From Gist", - "environments_from_gist_description": "Import Hoppscotch Environments From Gist", + "environments_from_gist": "Importálás Gist-ből", + "environments_from_gist_description": "Hoppscotch környezetek importálása Gist-ből", "failed": "Hiba az importálás során: a formátum nem azonosítható", - "from_file": "Import from File", - "from_gist": "Importálás Gistből", + "from_file": "Importálás fájlból", + "from_gist": "Importálás Gist-ből", "from_gist_description": "Importálás Gist URL-ből", "from_insomnia": "Importálás Insomniából", "from_insomnia_description": "Importálás Insomnia-gyűjteményből", @@ -391,45 +441,50 @@ "from_my_collections_description": "Importálás saját gyűjtemények fájlból", "from_openapi": "Importálás OpenAPI-ból", "from_openapi_description": "Importálás OpenAPI specifikációs fájlból (YML/JSON)", - "from_postman": "Importálás Postmanből", + "from_postman": "Importálás Postman-ből", "from_postman_description": "Importálás Postman-gyűjteményből", "from_url": "Importálás URL-ből", "gist_url": "Gist URL megadása", - "gql_collections_from_gist_description": "Import GraphQL Collections From Gist", - "hoppscotch_environment": "Hoppscotch Environment", - "hoppscotch_environment_description": "Import Hoppscotch Environment JSON file", + "gql_collections_from_gist_description": "GraphQL gyűjtemények importálása Gist-ből", + "hoppscotch_environment": "Hoppscotch környezet", + "hoppscotch_environment_description": "Hoppscotch környezet importálása JSON fájlból", "import_from_url_invalid_fetch": "Nem sikerült lekérni az adatokat az URL-ről", "import_from_url_invalid_file_format": "Hiba a gyűjtemények importálása során", "import_from_url_invalid_type": "Nem támogatott típus. Az elfogadott értékek: „hoppscotch”, „openapi”, „postman” vagy „insomnia”.", "import_from_url_success": "Gyűjtemények importálva", - "insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file", + "insomnia_environment_description": "Insomnia környezet importálása JSON/YAML fájlból", "json_description": "Gyűjtemények importálása Hoppscotch-gyűjtemények JSON-fájlból", - "postman_environment": "Postman Environment", - "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Importálás" + "postman_environment": "Postman környezet", + "postman_environment_description": "Postman környezet importálása JSON fájlból", + "title": "Importálás", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { - "description": "Inspect possible errors", + "description": "Lehetséges hibák ellenőrzése", "environment": { - "add_environment": "Add to Environment", - "not_found": "Environment variable “{environment}” not found." + "add_environment": "Hozzáadás a környezethez", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", + "not_found": "\"{environment}\" környezet nem található." }, "header": { - "cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." + "cookie": "A böngésző nem engedélyezi a Hoppscotch-nak, hogy süti fejlécet állítson be. Amíg dolgozunk a Hoppscotch asztali alkalmazáson (hamarosan), kérjük használjon Authorization Header fejlécet." }, "response": { - "401_error": "Please check your authentication credentials.", - "404_error": "Please check your request URL and method type.", - "cors_error": "Please check your Cross-Origin Resource Sharing configuration.", - "default_error": "Please check your request.", - "network_error": "Please check your network connection." + "401_error": "Kérjük ellenőrizze az autentikációs adatokat.", + "404_error": "Kérjük ellenőrizze a kérés URL-jét és típusát.", + "cors_error": "Kérjük ellenőrizze a Cross-Origin Resource Sharing beállítást.", + "default_error": "Kérjük ellenőrizze a kérését.", + "network_error": "Kérjük ellenőrizze a internet elérhetőségét." }, "title": "Inspector", "url": { - "extension_not_installed": "Extension not installed.", - "extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", - "extention_enable_action": "Enable Browser Extension", - "extention_not_enabled": "Extension not enabled." + "extension_not_installed": "Bővítmény nincs telepítve.", + "extension_unknown_origin": "Ellenőrizze, hogy hozzáadta az API végpont forrását Hoppscotch Browser bővítmény listájához.", + "extention_enable_action": "Bővítmény engedélyezése", + "extention_not_enabled": "Bővítmény nincs engedélyezve." } }, "layout": { @@ -443,10 +498,10 @@ "close_unsaved_tab": "Elmentetlen változtatásai vannak", "collections": "Gyűjtemények", "confirm": "Megerősítés", - "customize_request": "Customize Request", + "customize_request": "Kérés testreszabása", "edit_request": "Kérés szerkesztése", "import_export": "Importálás és exportálás", - "share_request": "Share Request" + "share_request": "Kérés megosztása" }, "mqtt": { "already_subscribed": "Ön már feliratkozott erre a témára.", @@ -512,7 +567,7 @@ }, "request": { "added": "Kérés hozzáadva", - "authorization": "Felhatalmazás", + "authorization": "Azonosítás", "body": "Kérés törzse", "choose_language": "Nyelv kiválasztása", "content_type": "Tartalom típusa", @@ -527,8 +582,8 @@ "enter_curl": "cURL-parancs megadása", "generate_code": "Kód előállítása", "generated_code": "Előállított kód", - "go_to_authorization_tab": "Go to Authorization tab", - "go_to_body_tab": "Go to Body tab", + "go_to_authorization_tab": "Navigálás az Azonosítás lapra", + "go_to_body_tab": "Navigálás a Törzs lapra.", "header_list": "Fejléclista", "invalid_name": "Adjon nevet a kérésnek", "method": "Módszer", @@ -547,17 +602,19 @@ "raw_body": "Nyers kéréstörzs", "rename": "Rename Request", "renamed": "Kérés átnevezve", + "request_variables": "Request variables", "run": "Futtatás", "save": "Mentés", "save_as": "Mentés másként", "saved": "Kérés elmentve", "share": "Megosztás", "share_description": "A Hoppscotch megosztása az ismerőseivel", - "share_request": "Share Request", - "stop": "Stop", + "share_request": "Kérés megosztása", + "stop": "Leállítás", "title": "Kérés", "type": "Kérés típusa", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Változók", "view_my_links": "Saját hivatkozások megtekintése", "copy_link": "Hivatkozás másolása" @@ -588,7 +645,7 @@ "account_description": "A fiókbeállítások személyre szabása.", "account_email_description": "Az Ön elsődleges e-mail-címe.", "account_name_description": "Ez a megjelenített neve.", - "additional": "Additional Settings", + "additional": "További beállítások", "background": "Háttér", "black_mode": "Fekete", "choose_language": "Nyelv kiválasztása", @@ -636,29 +693,29 @@ "verify_email": "E-mail-cím ellenőrzése" }, "shared_requests": { - "button": "Button", - "button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.", - "copy_html": "Copy HTML", - "copy_link": "Copy Link", - "copy_markdown": "Copy Markdown", - "creating_widget": "Creating widget", - "customize": "Customize", - "deleted": "Shared request deleted", - "description": "Select a widget, you can change and customize this later", - "embed": "Embed", - "embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.", + "button": "Gomb", + "button_info": "Hozza létre a 'Futtatás Hoppscotch-ban' gombot a weboldalán vagy blogján.", + "copy_html": "HTML másolása", + "copy_link": "Link másolása", + "copy_markdown": "Jelölő másolása", + "creating_widget": "Widget létrehozása", + "customize": "Testreszabás", + "deleted": "Megosztott kérés törölve", + "description": "Válasszon ki egy widgetet, ezt később módosíthatja és testreszabhatja", + "embed": "Beágyazás", + "embed_info": "Adja hozzá a \"Hoppscotch API Playground\"-ot weboldalához vagy blogjához.", "link": "Link", - "link_info": "Create a shareable link to share with anyone on the internet with view access.", - "modified": "Shared request modified", - "not_found": "Shared request not found", - "open_new_tab": "Open in new tab", - "preview": "Preview", - "run_in_hoppscotch": "Run in Hoppscotch", + "link_info": "Link létrehozása olvasási joggal való megosztáshoz.", + "modified": "Megosztott kérés módosítva", + "not_found": "Megosztott kérés nem található.", + "open_new_tab": "Megnyitás új lapon.", + "preview": "Előnézet", + "run_in_hoppscotch": "Futtatás Hoppscotch-ban.", "theme": { - "dark": "Dark", - "light": "Light", - "system": "System", - "title": "Theme" + "dark": "Sötét", + "light": "Világos", + "system": "Rendszer", + "title": "Téma" } }, "shortcut": { @@ -670,7 +727,7 @@ "title": "Általános" }, "miscellaneous": { - "invite": "Emberek meghívása a Hoppscotchba", + "invite": "Emberek meghívása a Hoppscotch-ba", "title": "Egyebek" }, "navigation": { @@ -692,19 +749,19 @@ "delete_method": "DELETE módszer kiválasztása", "get_method": "GET módszer kiválasztása", "head_method": "HEAD módszer kiválasztása", - "import_curl": "Import cURL", + "import_curl": "cURL importálása", "method": "Módszer", "next_method": "Következő módszer kiválasztása", "post_method": "POST módszer kiválasztása", "previous_method": "Előző módszer kiválasztása", "put_method": "PUT módszer kiválasztása", - "rename": "Rename Request", + "rename": "Kérés átnevezése", "reset_request": "Kérés visszaállítása", - "save_request": "Save Request", + "save_request": "Kérés mentése", "save_to_collections": "Mentés a gyűjteményekbe", "send_request": "Kérés elküldése", - "share_request": "Share Request", - "show_code": "Generate code snippet", + "share_request": "Kérés megosztása", + "show_code": "Kódrészlet generálása", "title": "Kérés", "copy_request_link": "Kérés hivatkozásának másolása" }, @@ -736,82 +793,89 @@ "url": "URL" }, "spotlight": { - "change_language": "Change Language", + "change_language": "Nyelv váltása", "environments": { - "delete": "Delete current environment", - "duplicate": "Duplicate current environment", - "duplicate_global": "Duplicate global environment", - "edit": "Edit current environment", - "edit_global": "Edit global environment", - "new": "Create new environment", - "new_variable": "Create a new environment variable", - "title": "Environments" + "delete": "Jelenlegi környezet törlése", + "duplicate": "Jelenlegi környezet duplikálása", + "duplicate_global": "Globális környezet duplikálása", + "edit": "Jelenlegi környezet szerkesztése", + "edit_global": "Globális környezet szerkesztése", + "new": "Új környezet létrehozása", + "new_variable": "Új környezeti változó létrehozása", + "title": "Környezetek" }, "general": { - "chat": "Chat with support", - "help_menu": "Help and support", - "open_docs": "Read Documentation", - "open_github": "Open GitHub repository", - "open_keybindings": "Keyboard shortcuts", - "social": "Social", - "title": "General" + "chat": "Üzenet a supportnak", + "help_menu": "Segítség és support", + "open_docs": "Dokumentáció olvasása", + "open_github": "GitHub repository megnyitása", + "open_keybindings": "Billentyűkombinációk megnyitása", + "social": "Közösség", + "title": "Általános" }, "graphql": { - "connect": "Connect to server", - "disconnect": "Disconnect from server" + "connect": "Csatlakozás a szerverhez", + "disconnect": "Lecsatlakozás a szerverről" }, "miscellaneous": { - "invite": "Invite your friends to Hoppscotch", - "title": "Miscellaneous" + "invite": "Hívja meg barátait a Hoppscotch-ba", + "title": "Egyéb" }, "request": { - "save_as_new": "Save as new request", - "select_method": "Select method", - "switch_to": "Switch to", - "tab_authorization": "Authorization tab", - "tab_body": "Body tab", - "tab_headers": "Headers tab", - "tab_parameters": "Parameters tab", - "tab_pre_request_script": "Pre-request script tab", - "tab_query": "Query tab", - "tab_tests": "Tests tab", - "tab_variables": "Variables tab" + "save_as_new": "Mentés új kérésként", + "select_method": "Módszer kiválasztása", + "switch_to": "Váltás", + "tab_authorization": "Azonosítás lap", + "tab_body": "Törzs lap", + "tab_headers": "Fejlécek lap", + "tab_parameters": "Paraméterek lap", + "tab_pre_request_script": "Előzetes script lap", + "tab_query": "Kérés lap", + "tab_tests": "Tesztek lap", + "tab_variables": "Változók lap" }, "response": { - "copy": "Copy response", - "download": "Download response as file", - "title": "Response" + "copy": "Válasz másolása", + "download": "Válasz letöltése fájlként", + "title": "Válasz" }, "section": { "interceptor": "Interceptor", "interface": "Interface", - "theme": "Theme", - "user": "User" + "theme": "Téma", + "user": "Felhasználó" }, "settings": { - "change_interceptor": "Change Interceptor", - "change_language": "Change Language", + "change_interceptor": "Interceptor váltása", + "change_language": "Nyelv váltása", "theme": { - "black": "Black", - "dark": "Dark", - "light": "Light", - "system": "System preference" + "black": "Fekete", + "dark": "Sötét", + "light": "Világos", + "system": "Rendszer" } }, "tab": { - "close_current": "Close current tab", - "close_others": "Close all other tabs", - "duplicate": "Duplicate current tab", - "new_tab": "Open a new tab", - "title": "Tabs" + "close_current": "Jelenlegi lap bezására", + "close_others": "Összes többi lap bezására", + "duplicate": "Jelenlegi lap diplikálása", + "new_tab": "Új lap megnyitása", + "title": "Lapok" }, "workspace": { - "delete": "Delete current team", - "edit": "Edit current team", - "invite": "Invite people to team", - "new": "Create new team", - "switch_to_personal": "Switch to your personal workspace", - "title": "Teams" + "delete": "Jelenlegi csapat törlése", + "edit": "Jelenlegi csapat szerkesztése", + "invite": "Emberek meghívása a jelenlegi csapatba", + "new": "Új csapat létrehozása", + "switch_to_personal": "Váltás a személyes munkaterületére", + "title": "Csapatok" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -829,20 +893,20 @@ "connection_error": "Nem sikerült kapcsolódni", "connection_failed": "A kapcsolódás sikertelen", "connection_lost": "A kapcsolat elveszett", - "copied_interface_to_clipboard": "Copied {language} interface type to clipboard", + "copied_interface_to_clipboard": "{language} interface típusa vágólapra másolva", "copied_to_clipboard": "Vágólapra másolva", "deleted": "Törölve", "deprecated": "ELAVULT", "disabled": "Letiltva", - "disconnected": "Leválasztva", - "disconnected_from": "Leválasztva innen: {name}", + "disconnected": "Lecsatlakoztatva", + "disconnected_from": "Lecsatlakoztatva innen: {name}", "docs_generated": "Dokumentáció előállítva", - "download_failed": "Download failed", + "download_failed": "Letöltés sikertelen", "download_started": "A letöltés elkezdődött", "enabled": "Engedélyezve", "file_imported": "Fájl importálva", "finished_in": "Befejeződött {duration} ms alatt", - "hide": "Hide", + "hide": "Elrejtés", "history_deleted": "Előzmények törölve", "linewrap": "Sorok tördelése", "loading": "Betöltés…", @@ -868,18 +932,18 @@ "forum": "Tegyen fel kérdéseket és kapjon válaszokat", "github": "Kövessen minket GitHubon", "shortcuts": "Az alkalmazás gyorsabb böngészése", - "team": "Vegye fel a kapcsolatot a csapattal", "title": "Támogatás", - "twitter": "Kövessen minket Twitteren" + "twitter": "Kövessen minket Twitteren", + "team": "Vegye fel a kapcsolatot a csapattal" }, "tab": { - "authorization": "Felhatalmazás", + "authorization": "Azonosítás", "body": "Törzs", - "close": "Close Tab", - "close_others": "Close other Tabs", + "close": "Lap bezárása", + "close_others": "Többi lap bezárása", "collections": "Gyűjtemények", "documentation": "Dokumentáció", - "duplicate": "Duplicate Tab", + "duplicate": "Lap duplikálása", "environments": "Környezetek", "headers": "Fejlécek", "history": "Előzmények", @@ -890,6 +954,9 @@ "query": "Lekérdezés", "schema": "Séma", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Tesztek", @@ -906,7 +973,7 @@ "email_do_not_match": "Az e-mail-cím nem egyezik a fiókja részleteivel. Vegye fel a kapcsolatot a csapat tulajdonosával.", "exit": "Kilépés a csapatból", "exit_disabled": "Csak a tulajdonos nem léphet ki a csapatból", - "failed_invites": "Failed invites", + "failed_invites": "Hiba a meghívás közben", "invalid_coll_id": "Érvénytelen gyűjteményazonosító", "invalid_email_format": "Az e-mail formátuma érvénytelen", "invalid_id": "Érvénytelen csapatazonosító. Vegye fel a kapcsolatot a csapat tulajdonosával.", @@ -918,7 +985,6 @@ "invite_tooltip": "Emberek meghívása erre a munkaterületre", "invited_to_team": "{owner} meghívta Önt, hogy csatlakozzon ehhez a csapathoz: {team}", "join": "Meghívás elfogadva", - "join_beta": "Csatlakozzon a béta programhoz, hogy hozzáférjen a csapatokhoz.", "join_team": "Csatlakozás ehhez: {team}", "joined_team": "Ön csatlakozott ehhez a csapathoz: {team}", "joined_team_description": "Ön mostantól ennek a csapatnak a tagja", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Csapatok", "we_sent_invite_link": "Elküldtünk egy meghívási hivatkozást az összes meghívottnak.", - "we_sent_invite_link_description": "Kérje meg az összes meghívottat, hogy nézzék meg a beérkező leveleiket. Kattintsanak a hivatkozásra a csapathoz való csatlakozáshoz." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Kérje meg az összes meghívottat, hogy nézzék meg a beérkező leveleiket. Kattintsanak a hivatkozásra a csapathoz való csatlakozáshoz.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Csatlakozzon a béta programhoz, hogy hozzáférjen a csapatokhoz." }, "team_environment": { "deleted": "Környezet törölve", @@ -978,9 +1049,50 @@ "workspace": { "change": "Munkaterület váltása", "personal": "Saját munkaterület", + "other_workspaces": "My Workspaces", "team": "Csapat-munkaterület", "title": "Munkaterületek" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Műveletek", "created_on": "Létrehozva", diff --git a/packages/hoppscotch-common/locales/id.json b/packages/hoppscotch-common/locales/id.json index 099b3fbd57..667deff9f6 100644 --- a/packages/hoppscotch-common/locales/id.json +++ b/packages/hoppscotch-common/locales/id.json @@ -24,8 +24,10 @@ "go_back": "Kembali", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Label", "learn_more": "Pelajari lebih lanjut", + "download_here": "Download here", "less": "Lebih sedikit", "more": "Lebih banyak", "new": "Baru", @@ -43,6 +45,7 @@ "search": "Cari", "send": "Kirim", "share": "Share", + "show_secret": "Show secret", "start": "Mulai", "starting": "Memulai", "stop": "Berhenti", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Ketik perintah atau cari…", "we_use_cookies": "Kami menggunakan cookie", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Apa yang baru?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Akun ada dengan kredensial berbeda - Masuk untuk menautkan kedua akun", "all_sign_in_options": "Semua opsi masuk", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Lanjutkan dengan Surel", "continue_with_github": "Lanjutkan dengan GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Lanjutkan dengan Google", "continue_with_microsoft": "Lanjutkan dengan Microsoft", "continue_with_oidc": "Lanjutkan dengan OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Lewat", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Kata Sandi", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Token", @@ -150,6 +178,7 @@ "different_parent": "Tidak dapat mengubah urutan koleksi dengan induk yang berbeda", "edit": "Mengubah Koleksi", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Berikan nama untuk Koleksi", "invalid_root_move": "Koleksi sudah berada di akar direktori", "moved": "Berhasil Dipindahkan", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Pilih Koleksi", "select_location": "Pilih lokasi", + "details": "Details", "select_team": "Pilih tim", "team_collections": "Koleksi Tim" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Apakah Anda yakin ingin menyisih dari Telemetri?", "request_change": "Apakah Anda yakin ingin membuang permintaan saat ini, perubahan yang belum disimpan akan hilang.", "save_unsaved_tab": "Apakah Anda ingin menyimpan perubahan yang dibuat di tab ini?", - "sync": "Apakah Anda ingin memulihkan ruang kerja Anda dari cloud? Ini akan membuang kemajuan lokal Anda." + "sync": "Apakah Anda ingin memulihkan ruang kerja Anda dari cloud? Ini akan membuang kemajuan lokal Anda.", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "Tidak ada undangan yang tertunda untuk tim ini", "profile": "Masuk untuk melihat profil Anda", "protocols": "Protokol kosong", + "request_variables": "This request does not have any request variables", "schema": "Hubungkan ke endpoint GraphQL untuk melihat skema", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Langganan kosong", "team_name": "Nama team kosong", "teams": "Kamu bukan di team manapun", "tests": "Tidak ada tes untuk permintaan ini", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes kosong" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Pilih environment", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment diperbarui", "value": "Value", "variable": "Variable", - "variable_list": "Daftar Variable" + "variables": "Variables", + "variable_list": "Daftar Variable", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Akun Anda saat ini merupakan pemilik dalam tim-tim ini:", "delete_account_description": "Anda harus menghapus diri Anda dari tim-tim ini, mentransfer kepemilikan, atau menghapus tim-tim ini sebelum Anda dapat menghapus akun Anda.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Nama Permintaan Kosong", "f12_details": "(F12 untuk detailnya)", "gql_prettify_invalid_query": "Tidak dapat prettify kueri yang tidak valid, menyelesaikan kesalahan sintaksis kueri, dan coba lagi", @@ -297,6 +337,7 @@ "incorrect_email": "Surel Salah", "invalid_link": "Tautan tidak valid", "invalid_link_description": "Tautan yang Anda klik tidak valid atau kedaluwarsa.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "JSON tidak valid", "json_prettify_invalid_body": "Tidak dapat prettify body yang tidak valid, selesaikan kesalahan sintaks json dan coba lagi", "network_error": "Sepertinya ada kesalahan jaringan. Silakan coba lagi.", @@ -308,17 +349,25 @@ "page_not_found": "Halaman ini tidak dapat ditemukan", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Tidak dapat menjalankan pre-request script", "something_went_wrong": "Ada yang salah", - "test_script_fail": "Tidak dapat mengeksekusi post-request script" + "test_script_fail": "Tidak dapat mengeksekusi post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Export sebagai JSON", "create_secret_gist": "Buat secret Gist", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist dibuat", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Masuk dengan GitHub untuk membuat secret gist", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Gist dibuat" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutations", "schema": "Schema", "subscriptions": "Subscriptions", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Impor Koleksi dari berkas JSON Koleksi Hoppscotch", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Impor" + "title": "Impor", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Raw Request Body", "rename": "Rename Request", "renamed": "Request berganti nama", + "request_variables": "Request variables", "run": "Jalankan", "save": "Menyimpan", "save_as": "Simpan sebagai", @@ -558,6 +614,7 @@ "title": "Request", "type": "Tipe Request", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Variables", "view_my_links": "Lihat tautan saya", "copy_link": "Salin tautan" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Ajukan pertanyaan dan dapatkan jawaban", "github": "Ikuti kami di GitHub", "shortcuts": "Jelajahi aplikasi lebih cepat", - "team": "Hubungi tim", "title": "Mendukung", - "twitter": "Ikuti kami di Twitter" + "twitter": "Ikuti kami di Twitter", + "team": "Hubungi tim" }, "tab": { "authorization": "Authorization", @@ -890,6 +954,9 @@ "query": "Query", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Tests", @@ -918,7 +985,6 @@ "invite_tooltip": "Undang orang ke ruang kerja ini", "invited_to_team": "{owner} mengundang Anda untuk bergabung {team}", "join": "Undangan diterima", - "join_beta": "Bergabunglah dengan program beta untuk mengakses tim.", "join_team": "Bergabung {team}", "joined_team": "Anda telah bergabung {team}", "joined_team_description": "Anda sekarang adalah anggota tim ini", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "tim", "we_sent_invite_link": "Kami mengirim tautan undangan ke semua undangan!", - "we_sent_invite_link_description": "Minta semua undangan untuk memeriksa kotak masuk mereka. Klik tautan untuk bergabung dengan tim." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Minta semua undangan untuk memeriksa kotak masuk mereka. Klik tautan untuk bergabung dengan tim.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Bergabunglah dengan program beta untuk mengakses tim." }, "team_environment": { "deleted": "Environment dihapus", @@ -978,9 +1049,50 @@ "workspace": { "change": "Beralih workspace", "personal": "Workspace Saya", + "other_workspaces": "My Workspaces", "team": "Workspace Tim", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Tindakan", "created_on": "Dibuat pada", diff --git a/packages/hoppscotch-common/locales/it.json b/packages/hoppscotch-common/locales/it.json index d3aa21eb0d..f3c8f14dc5 100644 --- a/packages/hoppscotch-common/locales/it.json +++ b/packages/hoppscotch-common/locales/it.json @@ -24,8 +24,10 @@ "go_back": "Torna indietro", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Etichetta", "learn_more": "Per saperne di più", + "download_here": "Download here", "less": "Less", "more": "Di più", "new": "Nuovo", @@ -43,6 +45,7 @@ "search": "Cerca", "send": "Invia", "share": "Share", + "show_secret": "Show secret", "start": "Avvia", "starting": "Starting", "stop": "Interrompi", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Digita un comando o cerca...", "we_use_cookies": "Utilizziamo i cookie", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Cosa c'è di nuovo?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "L'account esiste con credenziali diverse - Accedi per collegare entrambi gli account", "all_sign_in_options": "Tutte le opzioni di accesso", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Continua con e-mail", "continue_with_github": "Continua con GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Continua con Google", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "Continua con OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Password", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Token", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Modifica raccolta", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Si prega di fornire un nome valido per la raccolta", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Seleziona una raccolta", "select_location": "Seleziona la posizione", + "details": "Details", "select_team": "Seleziona un team", "team_collections": "Raccolte di team" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Sei sicuro di voler disattivare la telemetria?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Vuoi ripristinare il tuo spazio di lavoro con quello del cloud? Questo annullerà le tue modifiche fatte in locale." + "sync": "Vuoi ripristinare il tuo spazio di lavoro con quello del cloud? Questo annullerà le tue modifiche fatte in locale.", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "Non ci sono inviti pendenti per questo team", "profile": "Accedi per mostrare il tuo profilo", "protocols": "I protocolli sono vuoti", + "request_variables": "This request does not have any request variables", "schema": "Connettiti a un endpoint GraphQL per mostrare lo schema", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Il nome del team è vuoto", "teams": "I team sono vuoti", "tests": "Non ci sono test per questa richiesta", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Seleziona ambiente", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment updation", "value": "Value", "variable": "Variable", - "variable_list": "Elenco variabili" + "variables": "Variables", + "variable_list": "Elenco variabili", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Nome richiesta vuoto", "f12_details": "(F12 per i dettagli)", "gql_prettify_invalid_query": "Impossibile abbellire una query non valida, risolvere gli errori di sintassi della query e riprovare", @@ -297,6 +337,7 @@ "incorrect_email": "Indirizzo email errato", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "Impossibile abbellire un corpo non valido, risolvere gli errori di sintassi JSON e riprovare", "network_error": "Sembra ci sia un problema di rete. Per favore prova di nuovo.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Impossibile eseguire lo script di pre-richiesta", "something_went_wrong": "Qualcosa è andato storto", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Esporta come JSON", "create_secret_gist": "Crea un Gist segreto", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist creato", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Accedi con GitHub per creare un Gist segreto", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Gist creato" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutazioni", "schema": "Schema", "subscriptions": "Sottoscrizioni", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Importa" + "title": "Importa", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Corpo della richiesta non formattato", "rename": "Rename Request", "renamed": "Richiesta rinominata", + "request_variables": "Request variables", "run": "Esegui", "save": "Salva", "save_as": "Salva come", @@ -558,6 +614,7 @@ "title": "Richiesta", "type": "Tipo di richiesta", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Variabili", "view_my_links": "View my links", "copy_link": "Copia collegamento" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Fai domande e ottieni risposte", "github": "Follow us on Github", "shortcuts": "Naviga nella app più velocemente", - "team": "Mettiti in contatto con il team", "title": "Supporto", - "twitter": "Seguici su Twitter" + "twitter": "Seguici su Twitter", + "team": "Mettiti in contatto con il team" }, "tab": { "authorization": "Autorizzazione", @@ -890,6 +954,9 @@ "query": "Query", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Test", @@ -918,7 +985,6 @@ "invite_tooltip": "Invita persone in questo spazio di lavoro", "invited_to_team": "{owner} ti ha invitato ad unirti al team {team}", "join": "Invito accettato", - "join_beta": "Partecipa al programma beta per accedere ai team.", "join_team": "Unisciti al team {team}", "joined_team": "Sei parte del team {team}", "joined_team_description": "Ora sei un membro di questo team", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Team", "we_sent_invite_link": "Abbiamo inviato un link di invito a tutti gli invitati!", - "we_sent_invite_link_description": "Chiedi a tutti gli invitati di controllare la loro casella email. Cliccando sul link possono entrare nel team." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Chiedi a tutti gli invitati di controllare la loro casella email. Cliccando sul link possono entrare nel team.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Partecipa al programma beta per accedere ai team." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/ja.json b/packages/hoppscotch-common/locales/ja.json index bf018dc164..5138f58aaf 100644 --- a/packages/hoppscotch-common/locales/ja.json +++ b/packages/hoppscotch-common/locales/ja.json @@ -24,8 +24,10 @@ "go_back": "戻る", "go_forward": "Go forward", "group_by": "グループ化", + "hide_secret": "Hide secret", "label": "ラベル", "learn_more": "もっと詳しく", + "download_here": "Download here", "less": "表示を減らす", "more": "もっと見る", "new": "新規", @@ -43,6 +45,7 @@ "search": "検索", "send": "送信", "share": "Share", + "show_secret": "Show secret", "start": "はじめる", "starting": "開始中", "stop": "止める", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "コマンドもしくは検索ワードを入力…", "we_use_cookies": "Cookieを使用しています", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "新着情報", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "アカウントがそれぞれの認証情報で存在しています - ログインして両方のアカウントを連携する", "all_sign_in_options": "すべてのサインインオプション", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "メールアドレスで続行", "continue_with_github": "GitHubアカウントで続行", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Googleアカウントで続行", "continue_with_microsoft": "Microsoftアカウントで続行", "continue_with_oidc": "OIDCアカウントで続行", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "認証元: ", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "パスワード", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "トークン", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "コレクションの編集", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "コレクション名を入力してください", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "コレクションを選択", "select_location": "場所を選択", + "details": "Details", "select_team": "チームを選択", "team_collections": "チームコレクション" }, @@ -184,7 +214,8 @@ "remove_telemetry": "テレメトリをオプトアウトしてもよろしいですか?", "request_change": "現在のリクエストを削除してもよろしいですか?保存されていない変更は削除されます。", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "クラウドからワークスペースを復元しますか?この場合、ローカルの進行状況は破棄されます。" + "sync": "クラウドからワークスペースを復元しますか?この場合、ローカルの進行状況は破棄されます。", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "このチームに保留中の招待はありません", "profile": "ログインしてプロフィールを見る", "protocols": "プロトコルがありません", + "request_variables": "This request does not have any request variables", "schema": "GraphQLエンドポイントに接続する", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "サブスクリプションはありません", "team_name": "チーム名がありません", "teams": "チームに参加していません", "tests": "このリクエストのテストはありません", + "access_tokens": "Access tokens are empty", "shortcodes": "ショートコードはありません" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "環境変数を選択", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "環境変数を更新しました", "value": "Value", "variable": "Variable", - "variable_list": "環境変数リスト" + "variables": "Variables", + "variable_list": "環境変数リスト", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "危険", "delete_account": "あなたのアカウントは以下のチームのオーナーとなっています:", "delete_account_description": "アカウントを削除する前にチームを離脱するか、オーナーを委任するか、チームを削除してください。", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "リクエスト名がありません", "f12_details": "(詳細はF12キーを押してください)", "gql_prettify_invalid_query": "クエリを整形できませんでした。クエリの構文エラーを解決して再試行してください。", @@ -297,6 +337,7 @@ "incorrect_email": "メールアドレスが間違っています", "invalid_link": "リンクが無効です", "invalid_link_description": "クリックしたリンクは無効か期限切れです。", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "JSONが無効です", "json_prettify_invalid_body": "ボディを整形できませんでした。JSONの構文エラーを解決して再試行してください。", "network_error": "ネットワークエラーが発生したようです。もう一度お試しください。", @@ -308,17 +349,25 @@ "page_not_found": "このページは見つかりませんでした", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "リクエスト前のスクリプトを実行できませんでした", "something_went_wrong": "不明なエラーです", - "test_script_fail": "リクエスト後のスクリプトを実行できませんでした" + "test_script_fail": "リクエスト後のスクリプトを実行できませんでした", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "JSONとしてエクスポート", "create_secret_gist": "Secret Gistを作成", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gistが作成されました", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "GitHubにログインしてSecret Gistを作成", - "title": "エクスポート" + "title": "エクスポート", + "success": "Successfully exported", + "gist_created": "Gistが作成されました" }, "filter": { "all": "全て", @@ -340,7 +389,8 @@ "mutations": "ミューテーション", "schema": "スキーマ", "subscriptions": "サブスクリプション", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Hoppscotchのコレクション (JSONファイル) からインポート", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "インポート" + "title": "インポート", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "生のリクエストボディ", "rename": "Rename Request", "renamed": "リクエストの名前を変更", + "request_variables": "Request variables", "run": "実行", "save": "保存", "save_as": "名前を付けて保存", @@ -558,6 +614,7 @@ "title": "リクエスト", "type": "リクエストの種類", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "変数", "view_my_links": "自分のリンクを見る", "copy_link": "リンクをコピー" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "質問をして答えを得る", "github": "GitHubでフォローする", "shortcuts": "アプリをより効率よく使いこなす", - "team": "チームと連絡を取る", "title": "サポート", - "twitter": "私たちのTwitterをフォローする" + "twitter": "私たちのTwitterをフォローする", + "team": "チームと連絡を取る" }, "tab": { "authorization": "認証", @@ -890,6 +954,9 @@ "query": "クエリ", "schema": "スキーマ", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "テスト", @@ -918,7 +985,6 @@ "invite_tooltip": "このワークスペースに招待", "invited_to_team": "{owner}が{team}にあなたを招待しました", "join": "招待を了承しました", - "join_beta": "ベータプログラムに参加して、チームにアクセスする。", "join_team": "{team}に参加", "joined_team": "あなたは{team}に参加しました", "joined_team_description": "あなたはこのチームのメンバーです", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "チーム", "we_sent_invite_link": "招待者の皆様に、招待リンクを送信しました!", - "we_sent_invite_link_description": "招待者全員に受信トレイを確認するよう依頼します。リンクをクリックすると、チームに参加できます。" + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "招待者全員に受信トレイを確認するよう依頼します。リンクをクリックすると、チームに参加できます。", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "ベータプログラムに参加して、チームにアクセスする。" }, "team_environment": { "deleted": "環境変数を削除しました", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "アクション", "created_on": "作成日", diff --git a/packages/hoppscotch-common/locales/ko.json b/packages/hoppscotch-common/locales/ko.json index 42539b2a08..35b8f4580c 100644 --- a/packages/hoppscotch-common/locales/ko.json +++ b/packages/hoppscotch-common/locales/ko.json @@ -24,8 +24,10 @@ "go_back": "돌아가기", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "이름", "learn_more": "더 알아보기", + "download_here": "Download here", "less": "접기", "more": "더보기", "new": "추가", @@ -43,6 +45,7 @@ "search": "검색", "send": "보내기", "share": "Share", + "show_secret": "Show secret", "start": "시작", "starting": "Starting", "stop": "정지", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "명령을 입력하거나 검색...", "we_use_cookies": "우리는 쿠키를 사용중입니다.", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "새로 바뀐 점", + "see_whats_new": "See what’s new", "wiki": "위키" }, "auth": { "account_exists": "계정이 다른 자격 증명으로 존재합니다. 두 계정을 연결하려면 로그인하세요.", "all_sign_in_options": "모든 로그인 옵션", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "이메일로 계속하기", "continue_with_github": "GitHub로 계속하기", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Google로 계속하기", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "OIDC로 계속하기", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "전달 방식", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "비밀번호", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "토큰", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "모음집 편집", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "모음집 이름을 바르게 입력하세요.", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "모음집 선택", "select_location": "위치 선택", + "details": "Details", "select_team": "팀 선택", "team_collections": "팀 모음집" }, @@ -184,7 +214,8 @@ "remove_telemetry": "진단 데이터를 보내지 않겠습니까?", "request_change": "현재 요청을 취소하시겠습니까? 저장되지 않은 변경사항은 삭제됩니다.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "이 작업 공간을 동기화하겠습니까?" + "sync": "이 작업 공간을 동기화하겠습니까?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "이 팀에는 대기 중인 초대가 없습니다.", "profile": "로그인하여 프로필을 확인합니다.", "protocols": "프로토콜이 비어 있습니다.", + "request_variables": "This request does not have any request variables", "schema": "스키마를 보려면 GraphQL 엔드포인트에 연결하세요.", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "팀 이름이 비어 있습니다.", "teams": "아무 팀에도 속하지 않았습니다.", "tests": "이 요청에 대한 테스트가 없습니다.", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "환경 선택", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "환경 수정됨", "value": "Value", "variable": "Variable", - "variable_list": "변수 목록" + "variables": "Variables", + "variable_list": "변수 목록", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "빈 요청 이름", "f12_details": "(자세한 내용은 F12)", "gql_prettify_invalid_query": "잘못된 쿼리를 구문 강조할 수 없습니다. 쿼리 구문 오류를 해결하고 다시 시도하세요.", @@ -297,6 +337,7 @@ "incorrect_email": "잘못된 이메일 형식", "invalid_link": "잘못된 링크", "invalid_link_description": "잘못된 링크이거나 만료된 링크입니다.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "잘못된 본문을 구문 강조할 수 없습니다. json 구문 오류를 해결하고 다시 시도하세요.", "network_error": "네트워크 에러인 것 같습니다. 다시 시도하세요.", @@ -308,17 +349,25 @@ "page_not_found": "해당 페이지를 찾을 수 없습니다.", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "사전 요청 스크립트를 실행할 수 없습니다.", "something_went_wrong": "문제가 발생했습니다.", - "test_script_fail": "테스트 스크립트를 실행할 수 없습니다." + "test_script_fail": "테스트 스크립트를 실행할 수 없습니다.", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "JSON으로 내보내기", "create_secret_gist": "Secret Gist 만들기", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist가 생성됨", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "GitHub에 로그인하여 secret gist 만들기", - "title": "내보내기" + "title": "내보내기", + "success": "Successfully exported", + "gist_created": "Gist가 생성됨" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "뮤테이션", "schema": "스키마", "subscriptions": "섭스크립션", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "호프스카치 모음집 JSON 파일을 가져옵니다.", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "가져오기" + "title": "가져오기", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "원시 요청 본문", "rename": "Rename Request", "renamed": "요청 이름이 변경됨", + "request_variables": "Request variables", "run": "실행", "save": "저장", "save_as": "다른 이름으로 저장", @@ -558,6 +614,7 @@ "title": "제목", "type": "요청 유형", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "변수", "view_my_links": "내 링크 보기", "copy_link": "링크 복사" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "질문하고 답변 받기", "github": "Follow us on Github", "shortcuts": "더 빠르게 앱 탐색", - "team": "팀에 연락하기", "title": "지원", - "twitter": "트위터에서 팔로우" + "twitter": "트위터에서 팔로우", + "team": "팀에 연락하기" }, "tab": { "authorization": "인증", @@ -890,6 +954,9 @@ "query": "쿼리", "schema": "스키마", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "테스트", @@ -918,7 +985,6 @@ "invite_tooltip": "사람들을 이 작업 공간에 초대하기", "invited_to_team": "{owner}가 당신을 {team} 팀에 초대했습니다.", "join": "Invitation accepted", - "join_beta": "베타 프로그램에 참여하여 팀에 액세스하세요.", "join_team": "{team}에 합류", "joined_team": "{team}에 합류했습니다.", "joined_team_description": "이 팀의 구성원이 되었습니다.", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "팀", "we_sent_invite_link": "모든 초대 대상에게 초대 링크를 보냈습니다.", - "we_sent_invite_link_description": "초대 대상들이 이메일을 확인하도록 권하세요. 이메일 속 링크를 클릭하면 팀에 합류할 수 있습니다." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "초대 대상들이 이메일을 확인하도록 권하세요. 이메일 속 링크를 클릭하면 팀에 합류할 수 있습니다.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "베타 프로그램에 참여하여 팀에 액세스하세요." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/nl.json b/packages/hoppscotch-common/locales/nl.json index dd52d83989..d129ea63ba 100644 --- a/packages/hoppscotch-common/locales/nl.json +++ b/packages/hoppscotch-common/locales/nl.json @@ -24,8 +24,10 @@ "go_back": "Ga terug", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Label", "learn_more": "Leer meer", + "download_here": "Download here", "less": "Less", "more": "Meer", "new": "Nieuw", @@ -43,6 +45,7 @@ "search": "Zoeken", "send": "Versturen", "share": "Share", + "show_secret": "Show secret", "start": "Begin", "starting": "Starting", "stop": "Stop", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Typ een opdracht of zoek...", "we_use_cookies": "Wij gebruiken cookies", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Wat is er nieuw?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Account bestaat met verschillende inloggegevens. Log in om beide accounts te koppelen", "all_sign_in_options": "Alle aanmeldmogelijkheden", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Ga verder met e-mail", "continue_with_github": "Ga verder met GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Ga verder met Google", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "Ga verder met OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Wachtwoord", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "token", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Verzameling bewerken", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Geef een geldige naam op voor de collectie", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Selecteer een collectie", "select_location": "Selecteer een locatie", + "details": "Details", "select_team": "Selecteer een team", "team_collections": "Teamcollecties" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Weet u zeker dat u zich wilt afmelden voor telemetrie?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Weet u zeker dat u deze werkruimte wilt synchroniseren?" + "sync": "Weet u zeker dat u deze werkruimte wilt synchroniseren?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "There are no pending invites for this team", "profile": "Login to view your profile", "protocols": "Protocollen zijn leeg", + "request_variables": "This request does not have any request variables", "schema": "Verbinding maken met een GraphQL-eindpunt", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Teamnaam leeg", "teams": "Teams zijn leeg", "tests": "Er zijn geen tests voor dit verzoek", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Selecteer omgeving", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment updation", "value": "Value", "variable": "Variable", - "variable_list": "Variabele lijst" + "variables": "Variables", + "variable_list": "Variabele lijst", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Lege aanvraagnaam", "f12_details": "(F12 voor details)", "gql_prettify_invalid_query": "Kon een ongeldige zoekopdracht niet mooier maken, syntaxisfouten in de query oplossen en opnieuw proberen", @@ -297,6 +337,7 @@ "incorrect_email": "Incorrect email", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "Kon een ongeldige hoofdtekst niet mooier maken, json-syntaxisfouten oplossen en opnieuw proberen", "network_error": "There seems to be a network error. Please try again.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Kon pre-aanvraagscript niet uitvoeren", "something_went_wrong": "Er is iets fout gegaan", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Exporteren als JSON", "create_secret_gist": "Maak geheime kern", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Kern gemaakt", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Log in met GitHub om een geheime kern te maken", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Kern gemaakt" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutaties", "schema": "Schema", "subscriptions": "Abonnementen", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Importeren" + "title": "Importeren", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Ruwe body", "rename": "Rename Request", "renamed": "Verzoek hernoemd", + "request_variables": "Request variables", "run": "Uitvoeren", "save": "Opslaan", "save_as": "Opslaan als", @@ -558,6 +614,7 @@ "title": "Verzoek", "type": "Aanvraag type", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Variabelen", "view_my_links": "View my links", "copy_link": "Kopieer link" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Stel vragen en krijg antwoorden", "github": "Follow us on Github", "shortcuts": "Sneller door de app bladeren", - "team": "Neem contact op met het team", "title": "Steun", - "twitter": "Volg ons op Twitter" + "twitter": "Volg ons op Twitter", + "team": "Neem contact op met het team" }, "tab": { "authorization": "Autorisatie", @@ -890,6 +954,9 @@ "query": "Query", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Testen", @@ -918,7 +985,6 @@ "invite_tooltip": "Invite people to this workspace", "invited_to_team": "{owner} invited you to join {team}", "join": "Invitation accepted", - "join_beta": "Doe mee aan het bètaprogramma om toegang te krijgen tot teams.", "join_team": "Join {team}", "joined_team": "You have joined {team}", "joined_team_description": "You are now a member of this team", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "teams", "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Doe mee aan het bètaprogramma om toegang te krijgen tot teams." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/no.json b/packages/hoppscotch-common/locales/no.json index 6ff92016f8..e92dd28c06 100644 --- a/packages/hoppscotch-common/locales/no.json +++ b/packages/hoppscotch-common/locales/no.json @@ -24,8 +24,10 @@ "go_back": "Gå tilbake", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Merkelapp", "learn_more": "Lær mer", + "download_here": "Download here", "less": "Less", "more": "Mer", "new": "Ny", @@ -43,6 +45,7 @@ "search": "Søk", "send": "Sende", "share": "Share", + "show_secret": "Show secret", "start": "Start", "starting": "Starting", "stop": "Stoppe", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Skriv inn en kommando eller søk ...", "we_use_cookies": "Vi bruker informasjonskapsler", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Hva er nytt?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Kontoen eksisterer med annen legitimasjon - Logg på for å koble begge kontoene", "all_sign_in_options": "Alle påloggingsalternativer", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Fortsett med e-post", "continue_with_github": "Fortsett med GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Fortsett med Google", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "Fortsett med OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Passord", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Nøkkel", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Rediger samling", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Oppgi et gyldig navn på samlingen", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Velg en samling", "select_location": "Velg plassering", + "details": "Details", "select_team": "Velg et lag", "team_collections": "Lagsamlinger" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Er du sikker på at du vil velge bort telemetri?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Er du sikker på at du vil synkronisere dette arbeidsområdet?" + "sync": "Er du sikker på at du vil synkronisere dette arbeidsområdet?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "There are no pending invites for this team", "profile": "Login to view your profile", "protocols": "Protokoller er tomme", + "request_variables": "This request does not have any request variables", "schema": "Koble til et GraphQL-endepunkt", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Lagnavnet er tomt", "teams": "Lagene er tomme", "tests": "Det er ingen tester for denne forespørselen", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Velg miljø", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment updation", "value": "Value", "variable": "Variable", - "variable_list": "Variabel liste" + "variables": "Variables", + "variable_list": "Variabel liste", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Tom forespørselsnavn", "f12_details": "(F12 for detaljer)", "gql_prettify_invalid_query": "Kunne ikke forskjønne en ugyldig spørring, løse spørringssyntaksfeil og prøve igjen", @@ -297,6 +337,7 @@ "incorrect_email": "Incorrect email", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "Kunne ikke forskjønne et ugyldig brødtekst, løse json -syntaksfeil og prøve igjen", "network_error": "There seems to be a network error. Please try again.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Kunne ikke kjøre forhåndsforespørselsskript", "something_went_wrong": "Noe gikk galt", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Eksporter som JSON", "create_secret_gist": "Lag hemmelig Gist", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist opprettet", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Logg på med GitHub for å lage en hemmelig oppgave", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Gist opprettet" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutasjoner", "schema": "Skjema", "subscriptions": "Abonnementer", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Import" + "title": "Import", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Raw Request Body", "rename": "Rename Request", "renamed": "Forespørsel omdøpt", + "request_variables": "Request variables", "run": "Løpe", "save": "Lagre", "save_as": "Lagre som", @@ -558,6 +614,7 @@ "title": "Be om", "type": "Type forespørsel", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Variabler", "view_my_links": "View my links", "copy_link": "Kopier link" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Still spørsmål og få svar", "github": "Follow us on Github", "shortcuts": "Bla gjennom appen raskere", - "team": "Ta kontakt med teamet", "title": "Brukerstøtte", - "twitter": "Følg oss på Twitter" + "twitter": "Følg oss på Twitter", + "team": "Ta kontakt med teamet" }, "tab": { "authorization": "Autorisasjon", @@ -890,6 +954,9 @@ "query": "Spørsmål", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Tester", @@ -918,7 +985,6 @@ "invite_tooltip": "Invite people to this workspace", "invited_to_team": "{owner} invited you to join {team}", "join": "Invitation accepted", - "join_beta": "Bli med i betaprogrammet for å få tilgang til lag.", "join_team": "Join {team}", "joined_team": "You have joined {team}", "joined_team_description": "You are now a member of this team", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Lag", "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Bli med i betaprogrammet for å få tilgang til lag." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/pl.json b/packages/hoppscotch-common/locales/pl.json index 09be35f417..f853383e46 100644 --- a/packages/hoppscotch-common/locales/pl.json +++ b/packages/hoppscotch-common/locales/pl.json @@ -24,8 +24,10 @@ "go_back": "Wróć", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Etykieta", "learn_more": "Dowiedz się więcej", + "download_here": "Download here", "less": "Less", "more": "Więcej", "new": "Nowa", @@ -43,6 +45,7 @@ "search": "Szukaj", "send": "Wyślij", "share": "Share", + "show_secret": "Show secret", "start": "Rozpocznij", "starting": "Starting", "stop": "Zatrzymaj", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Wpisz polecenie lub wyszukaj…", "we_use_cookies": "Używamy plików cookie", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Co nowego?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Konto istnieje z różnymi danymi uwierzytelniającymi - Zaloguj się, aby połączyć oba konta", "all_sign_in_options": "Wszystkie opcje logowania", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Kontynuuj z e-mailem", "continue_with_github": "Kontynuuj z GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Kontynuuj z Google", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "Kontynuuj z OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Hasło", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Token", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Edytuj kolekcję", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Podaj prawidłową nazwę kolekcji", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Wybierz kolekcję", "select_location": "Wybierz lokalizację", + "details": "Details", "select_team": "Wybierz zespół", "team_collections": "Kolekcje zespołowe" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Czy na pewno chcesz zrezygnować z telemetrii?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Czy na pewno chcesz zsynchronizować ten obszar roboczy?" + "sync": "Czy na pewno chcesz zsynchronizować ten obszar roboczy?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "There are no pending invites for this team", "profile": "Login to view your profile", "protocols": "Protokoły są puste", + "request_variables": "This request does not have any request variables", "schema": "Połącz się z punktem końcowym GraphQL", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Nazwa zespołu jest pusta", "teams": "Zespoły są puste", "tests": "Nie ma testów dla tego żądania", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Wybierz środowisko", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment updation", "value": "Value", "variable": "Variable", - "variable_list": "Lista zmiennych" + "variables": "Variables", + "variable_list": "Lista zmiennych", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Pusta nazwa żądania", "f12_details": "(F12 po szczegóły)", "gql_prettify_invalid_query": "Nie można poprawić czytelności nieprawidłowego zapytania, napraw błędy składni zapytania i spróbuj ponownie", @@ -297,6 +337,7 @@ "incorrect_email": "Incorrect email", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "Nie można poprawić czytelności nieprawidłowej treści, napraw błędy składni json i spróbuj ponownie", "network_error": "There seems to be a network error. Please try again.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Nie można wykonać skryptu żądania wstępnego", "something_went_wrong": "Coś poszło nie tak", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Eksportuj jako JSON", "create_secret_gist": "Utwórz tajny Gist", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Utworzono Gist", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Zaloguj się za pomocą GitHub, aby utworzyć tajny Gist", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Utworzono Gist" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutacje", "schema": "Schemat", "subscriptions": "Subskrypcje", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Import" + "title": "Import", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Surowa treść żądania", "rename": "Rename Request", "renamed": "Zmieniono nazwę żądania", + "request_variables": "Request variables", "run": "Uruchom", "save": "Zapisz", "save_as": "Zapisz jako", @@ -558,6 +614,7 @@ "title": "Żądanie", "type": "Typ żądania", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Zmienne", "view_my_links": "View my links", "copy_link": "Skopiuj link" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Zadawaj pytania i otrzymuj odpowiedzi", "github": "Follow us on Github", "shortcuts": "Przeglądaj aplikację szybciej", - "team": "Skontaktuj się z zespołem", "title": "Wsparcie", - "twitter": "Śledź nas na Twitterze" + "twitter": "Śledź nas na Twitterze", + "team": "Skontaktuj się z zespołem" }, "tab": { "authorization": "Autoryzacja", @@ -890,6 +954,9 @@ "query": "Zapytanie", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Testy", @@ -918,7 +985,6 @@ "invite_tooltip": "Invite people to this workspace", "invited_to_team": "{owner} invited you to join {team}", "join": "Invitation accepted", - "join_beta": "Dołącz do programu beta, aby uzyskać dostęp do zespołów.", "join_team": "Join {team}", "joined_team": "You have joined {team}", "joined_team_description": "You are now a member of this team", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Zespoły", "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Dołącz do programu beta, aby uzyskać dostęp do zespołów." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/pt-br.json b/packages/hoppscotch-common/locales/pt-br.json index fc3def340d..dff8c0e81b 100644 --- a/packages/hoppscotch-common/locales/pt-br.json +++ b/packages/hoppscotch-common/locales/pt-br.json @@ -1,31 +1,33 @@ { "action": { - "add": "Add", - "autoscroll": "Autoscroll", + "add": "Adicionar", + "autoscroll": "Rolagem automática", "cancel": "Cancelar", "choose_file": "Escolha um arquivo", "clear": "Limpar", "clear_all": "Limpar tudo", - "clear_history": "Clear all History", - "close": "Close", + "clear_history": "Limpar o Histórico", + "close": "Fechar", "connect": "Conectar", - "connecting": "Connecting", + "connecting": "Conectando", "copy": "Copiar", - "create": "Create", + "create": "Criar", "delete": "Excluir", "disconnect": "Desconectar", "dismiss": "Dispensar", "dont_save": "Não Salvar", "download_file": "⇬ Fazer download do arquivo", - "drag_to_reorder": "Drag to reorder", + "drag_to_reorder": "Arrastar para reordenar", "duplicate": "Duplicar", "edit": "Editar", - "filter": "Filter", + "filter": "Filtrar", "go_back": "Voltar", - "go_forward": "Go forward", - "group_by": "Group by", + "go_forward": "Avançar", + "group_by": "Agrupar por", + "hide_secret": "Hide secret", "label": "Etiqueta", "learn_more": "Saber mais", + "download_here": "Download here", "less": "Menos", "more": "Mais", "new": "Novo", @@ -33,19 +35,20 @@ "open_workspace": "Open workspace", "paste": "Colar", "prettify": "Embelezar", - "properties": "Properties", + "properties": "Propriedades", "remove": "Remover", "rename": "Rename", "restore": "Restaurar", "save": "Salvar", - "scroll_to_bottom": "Scroll to bottom", - "scroll_to_top": "Scroll to top", - "search": "Procurar", + "scroll_to_bottom": "Ir para o final", + "scroll_to_top": "Ir para o topo", + "search": "Buscar", "send": "Enviar", - "share": "Share", + "share": "Compartilhar", + "show_secret": "Show secret", "start": "Começar", - "starting": "Starting", - "stop": "Pare", + "starting": "Iniciando", + "stop": "Parar", "to_close": "Fechar", "to_navigate": "Navegar", "to_select": "Selecionar", @@ -60,10 +63,10 @@ }, "app": { "chat_with_us": "Converse conosco", - "contact_us": "Contate-Nos", + "contact_us": "Contate-nos", "cookies": "Cookies", "copy": "Copiar", - "copy_interface_type": "Copy interface type", + "copy_interface_type": "Copiar tipo de interface", "copy_user_id": "Copiar token de autenticação do usuário", "developer_option": "Opções de desenvolvedor", "developer_option_description": "Opções de desenvolvedor que ajudam no desenvolvimento e manutenção do Hoppscotch.", @@ -71,40 +74,44 @@ "documentation": "Documentação", "github": "GitHub", "help": "Ajuda, feedback e documentação", - "home": "Lar", + "home": "Página Inicial", "invite": "Convidar", "invite_description": "No Hoppscotch, projetamos uma interface simples e intuitiva para criar e gerenciar suas APIs. Hoppscotch é uma ferramenta que o ajuda a construir, testar, documentar e compartilhar suas APIs.", "invite_your_friends": "Convide seus amigos", "join_discord_community": "Junte-se à nossa comunidade Discord", "keyboard_shortcuts": "Atalhos do teclado", "name": "Hoppscotch", - "new_version_found": "Nova versão encontrada. Atualize para atualizar.", - "open_in_hoppscotch": "Open in Hoppscotch", + "new_version_found": "Nova versão encontrada. Recarregue para atualizar.", + "open_in_hoppscotch": "Abrir no Hoppscotch", "options": "Opções", "proxy_privacy_policy": "Política de privacidade do proxy", "reload": "Recarregar", "search": "Procurar", "share": "Compartilhado", "shortcuts": "Atalhos", - "social_description": "Follow us on social media to stay updated with the latest news, updates and releases.", - "social_links": "Social links", - "spotlight": "Holofote", + "social_description": "Siga nossas redes sociais para acompanhar as últimas notícias, atualizações e lançamentos.", + "social_links": "Redes sociais", + "spotlight": "Destaque", "status": "Estado", "status_description": "Cheque o estado do website.", "terms_and_privacy": "Termos e privacidade", "twitter": "Twitter", "type_a_command_search": "Digite um comando ou pesquise...", "we_use_cookies": "Usamos cookies", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "O que há de novo?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "A conta existe com credenciais diferentes - Faça login para vincular as duas contas", "all_sign_in_options": "Todas as opções de login", - "continue_with_email": "Continue com Email", - "continue_with_github": "Continue com GitHub", - "continue_with_google": "Continue com o Google", - "continue_with_microsoft": "Continue with Microsoft", + "continue_with_auth_provider": "Continue with {provider}", + "continue_with_email": "Continuar com Email", + "continue_with_github": "Continuar com GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", + "continue_with_google": "Continuar com o Google", + "continue_with_microsoft": "Continuar com Microsoft", "continue_with_oidc": "Continue com OIDC", "email": "E-mail", "logged_out": "Desconectado", @@ -120,58 +127,81 @@ }, "authorization": { "generate_token": "Gerar token", - "graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", - "include_in_url": "Incluir no URL", - "inherited_from": "Inherited from {auth} from Parent Collection {collection} ", + "graphql_headers": "Cabeçalhos de Autorização são enviados como parte do payload de connection_init", + "include_in_url": "Incluir na URL", + "inherited_from": "Herdado {auth} da Coleção Pai {collection}", "learn": "Aprenda como", "oauth": { - "redirect_auth_server_returned_error": "Auth Server returned an error state", - "redirect_auth_token_request_failed": "Request to get the auth token failed", - "redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token", - "redirect_invalid_state": "Invalid State value present in the redirect", - "redirect_no_auth_code": "No Authorization Code present in the redirect", - "redirect_no_client_id": "No Client ID defined", - "redirect_no_client_secret": "No Client Secret Defined", - "redirect_no_code_verifier": "No Code Verifier Defined", - "redirect_no_token_endpoint": "No Token Endpoint Defined", - "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", - "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "redirect_auth_server_returned_error": "O servidor de autenticação retornou um estado de erro.", + "redirect_auth_token_request_failed": "Falha na requisição do token de autenticação", + "redirect_auth_token_request_invalid_response": "Resposta inválida do endpoint de token ao solicitar um token de autenticação", + "redirect_invalid_state": "Valor de estado inválido presente no redirecionamento", + "redirect_no_auth_code": "Nenhum código de Autorização presente no redirecionamento", + "redirect_no_client_id": "Nenhum ID de Cliente definido", + "redirect_no_client_secret": "Nenhum segredo de Cliente definido", + "redirect_no_code_verifier": "Nenhum verificador de código definido", + "redirect_no_token_endpoint": "Nenhum endpoint de token definido", + "something_went_wrong_on_oauth_redirect": "Algo deu errado no redirecionamento OAuth", + "something_went_wrong_on_token_generation": "Algo deu errado na geração do token", + "token_generation_oidc_discovery_failed": "Falha na geração do token: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, - "pass_key_by": "Pass by", + "pass_key_by": "Passar por", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Senha", - "save_to_inherit": "Please save this request in any collection to inherit the authorization", + "save_to_inherit": "Por favor, salve essa requisição em alguma coleção para herdar a autorização", "token": "Símbolo", "type": "Tipo de Autorização", "username": "Nome do usuário" }, "collection": { "created": "Coleção criada", - "different_parent": "Cannot reorder collection with different parent", + "different_parent": "Não é possível reordenar coleções com Coleções Pai diferentes", "edit": "Editar coleção", - "import_or_create": "Import or create a collection", + "import_or_create": "Importar ou criar uma coleção", + "import_collection": "Import Collection", "invalid_name": "Forneça um nome válido para a coleção", - "invalid_root_move": "Collection already in the root", - "moved": "Moved Successfully", + "invalid_root_move": "A coleção já está na raiz", + "moved": "Movido com sucesso", "my_collections": "Minhas coleções", "name": "Minha nova coleção", "name_length_insufficient": "O nome da coleção deve ter pelo menos 3 caracteres", "new": "Nova coleção", - "order_changed": "Collection Order Updated", - "properties": "Collection Properties", - "properties_updated": "Collection Properties Updated", + "order_changed": "Ordem das coleções atualizada", + "properties": "Propriedades da coleção", + "properties_updated": "Propriedades da coleção atualizadas", "renamed": "Coleção renomeada", "request_in_use": "Requisição em uso", "save_as": "Salvar como", - "save_to_collection": "Save to Collection", + "save_to_collection": "Salvar na Coleção", "select": "Selecione uma coleção", "select_location": "Selecione a localização", + "details": "Details", "select_team": "Selecione uma equipe", "team_collections": "Coleções da equipe" }, "confirm": { - "close_unsaved_tab": "Are you sure you want to close this tab?", - "close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", + "close_unsaved_tab": "Tem certeza de que deseja fechar esta aba?", + "close_unsaved_tabs": "Tem certeza de que deseja fechar todas as abas? {count} abas não salvas serão perdidas.", "exit_team": "Tem certeza que deseja sair desta equipe?", "logout": "Tem certeza que deseja sair?", "remove_collection": "Tem certeza de que deseja excluir esta coleção permanentemente?", @@ -179,34 +209,35 @@ "remove_folder": "Tem certeza de que deseja excluir esta pasta permanentemente?", "remove_history": "Tem certeza de que deseja excluir permanentemente todo o histórico?", "remove_request": "Tem certeza de que deseja excluir permanentemente esta solicitação?", - "remove_shared_request": "Are you sure you want to permanently delete this shared request?", + "remove_shared_request": "Tem certeza de que deseja excluir permanentemente esta requisição compartilhada?", "remove_team": "Tem certeza que deseja excluir esta equipe?", "remove_telemetry": "Tem certeza de que deseja cancelar a telemetria?", "request_change": "Tem certeza que deseja descartar a requisição atual? Alterações não salvas serão perdidas.", - "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Tem certeza de que deseja sincronizar este espaço de trabalho?" + "save_unsaved_tab": "Deseja salvar as atualizações feitas nesta aba?", + "sync": "Tem certeza de que deseja sincronizar este espaço de trabalho?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { - "add_parameters": "Add to parameters", - "open_request_in_new_tab": "Open request in new tab", - "set_environment_variable": "Set as variable" + "add_parameters": "Adicionar aos parâmetros", + "open_request_in_new_tab": "Abrir requisição em nova aba", + "set_environment_variable": "Configurar como variável" }, "cookies": { "modal": { - "cookie_expires": "Expires", - "cookie_name": "Name", - "cookie_path": "Path", + "cookie_expires": "Expira em", + "cookie_name": "Nome", + "cookie_path": "Caminho", "cookie_string": "Cookie string", - "cookie_value": "Value", - "empty_domain": "Domain is empty", - "empty_domains": "Domain list is empty", - "enter_cookie_string": "Enter cookie string", - "interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.", - "managed_tab": "Managed", - "new_domain_name": "New domain name", - "no_cookies_in_domain": "No cookies set for this domain", - "raw_tab": "Raw", - "set": "Set a cookie" + "cookie_value": "Valor", + "empty_domain": "Domínio vazio", + "empty_domains": "Lista de domínios vazia", + "enter_cookie_string": "Digite a string do cookie", + "interceptor_no_support": "Seu interceptor atualmente selecionado não suporta cookies. Selecione um Interceptor diferente e tente novamente.", + "managed_tab": "Gerenciado", + "new_domain_name": "Novo nome de domínio", + "no_cookies_in_domain": "Nenhum cookie definido para este domínio", + "raw_tab": "Bruto", + "set": "Definir um cookie" } }, "count": { @@ -226,7 +257,7 @@ "body": "Este pedido não tem corpo", "collection": "Coleção está vazia", "collections": "Coleções estão vazias", - "documentation": "Se conecte à um endpoint GraphQL para ver a documentação", + "documentation": "Se conecte a um endpoint GraphQL para ver a documentação", "endpoint": "O endpoint não pode ser vazio", "environments": "Ambientes estão vazios", "folder": "Pasta está vazia", @@ -238,13 +269,16 @@ "pending_invites": "Não há convites pendentes para esta equipe", "profile": "Entre para visualizar seu perfil", "protocols": "Os protocolos estão vazios", + "request_variables": "This request does not have any request variables", "schema": "Conecte-se a um endpoint GraphQL", - "shared_requests": "Shared requests are empty", - "shared_requests_logout": "Login to view your shared requests or create a new one", - "subscription": "Subscriptions are empty", + "secret_environments": "Secrets are not synced to Hoppscotch", + "shared_requests": "Lista de requisições compartilhadas vazia", + "shared_requests_logout": "Faça Login para visualizar suas requisições compartilhadas ou crie uma nova", + "subscription": "Lista de inscrições vazia", "team_name": "Nome da equipe vazio", "teams": "Equipes estão vazias", "tests": "Não há testes para esta requisição", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -253,43 +287,49 @@ "create_new": "Crie um novo ambiente", "created": "Ambiente criado", "deleted": "Deleção de ambiente", - "duplicated": "Environment duplicated", + "duplicated": "Ambiente duplicado", "edit": "Editar Ambiente", - "empty_variables": "No variables", + "empty_variables": "Nenhuma variável", "global": "Global", - "global_variables": "Global variables", - "import_or_create": "Import or create a environment", + "global_variables": "Variáveis globais", + "import_or_create": "Importar ou criar um ambiente", "invalid_name": "Forneça um nome válido para o ambiente", - "list": "Environment variables", - "my_environments": "My Environments", + "list": "Variáveis de ambiente", + "my_environments": "Meus Ambientes", "name": "Name", "nested_overflow": "Variáveis de ambiente aninhadas são limitadas a 10 níveis", "new": "Novo ambiente", - "no_active_environment": "No active environment", + "no_active_environment": "Nenhum ambiente ativo", "no_environment": "Sem ambiente", "no_environment_description": "Nenhum ambiente foi selecionado. Escolha o que fazer com as seguintes variáveis.", - "quick_peek": "Environment Quick Peek", - "replace_with_variable": "Replace with variable", - "scope": "Scope", + "quick_peek": "Visualização rápida do ambiente", + "replace_with_variable": "Substituir por variável", + "scope": "Escopo", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Selecione o ambiente", - "set": "Set environment", - "set_as_environment": "Set as environment", + "set": "Definir ambiente", + "set_as_environment": "Definir como ambiente", "team_environments": "Team Environments", "title": "Ambientes", "updated": "Atualizacao de ambientes", - "value": "Value", - "variable": "Variable", - "variable_list": "Lista de Variáveis" + "value": "Valor", + "variable": "Variável", + "variables": "Variables", + "variable_list": "Lista de Variáveis", + "properties": "Environment Properties", + "details": "Details" }, "error": { - "authproviders_load_error": "Unable to load auth providers", + "authproviders_load_error": "Não foi possível carregar os provedores de autenticação", "browser_support_sse": "Este navegador não parece ter suporte para eventos enviados pelo servidor.", "check_console_details": "Verifique o log do console para obter detalhes.", "check_how_to_add_origin": "Check how you can add an origin", "curl_invalid_format": "cURL não está formatado corretamente", "danger_zone": "Danger zone", - "delete_account": "Your account is currently an owner in these teams:", - "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "delete_account": "Atualmente sua conta é proprietária nesses times:", + "delete_account_description": "Você precisa sair, transferir para outro proprietário ou remover essas equipes antes de excluir sua conta.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Nome de requisição vazio", "f12_details": "(F12 para detalhes)", "gql_prettify_invalid_query": "Não foi possível justificar uma requisição inválida, resolva os erros de sintaxe da requisição e tente novamente", @@ -297,33 +337,42 @@ "incorrect_email": "Email incorreto", "invalid_link": "Link inválido", "invalid_link_description": "O link que você clicou é inválido ou já expirou.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "Não foi possível embelezar um corpo inválido, resolver erros de sintaxe json e tentar novamente", "network_error": "Parece que houve um problema de rede. Por favor, tente novamente.", "network_fail": "Não foi possível enviar requisição", - "no_collections_to_export": "No collections to export. Please create a collection to get started.", + "no_collections_to_export": "Nenhuma coleção para exportar. Por favor, crie uma coleção para iniciar.", "no_duration": "Sem duração", - "no_environments_to_export": "No environments to export. Please create an environment to get started.", - "no_results_found": "No matches found", - "page_not_found": "This page could not be found", + "no_environments_to_export": "Nenhum ambiente para exportar. Por favor, crie um ambiente para iniciar.", + "no_results_found": "Nenhuma correspondência encontrada", + "page_not_found": "Esta página não foi encontrada", "please_install_extension": "Please install the extension and add origin to the extension.", - "proxy_error": "Proxy error", + "proxy_error": "Erro de Proxy", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Não foi possível executar o script pré-requisição", "something_went_wrong": "Algo deu errado", - "test_script_fail": "Não foi possível executar o script pós-requisição" + "test_script_fail": "Não foi possível executar o script pós-requisição", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Exportar como JSON", "create_secret_gist": "Crie um gist secreto", - "failed": "Something went wrong while exporting", - "gist_created": "Gist criado", + "create_secret_gist_tooltip_text": "Export as secret Gist", + "failed": "Algo deu errado ao exportar", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Faça login com GitHub para criar um gist secreta", - "title": "Exportar" + "title": "Exportar", + "success": "Successfully exported", + "gist_created": "Gist criado" }, "filter": { - "all": "All", - "none": "None", - "starred": "Starred" + "all": "Todos", + "none": "Nenhum", + "starred": "Favoritos" }, "folder": { "created": "Pasta criada", @@ -340,10 +389,11 @@ "mutations": "Mutações", "schema": "Esquema", "subscriptions": "Assinaturas", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { - "title": "GraphQL Collections" + "title": "Coleções GraphQL" }, "group": { "time": "Time", @@ -356,10 +406,10 @@ }, "helpers": { "authorization": "O cabeçalho da autorização será gerado automaticamente quando você enviar a solicitação.", - "collection_properties_authorization": " This authorization will be set for every request in this collection.", - "collection_properties_header": "This header will be set for every request in this collection.", + "collection_properties_authorization": "Esta autorização será aplicada a todas as requisições nesta coleção.", + "collection_properties_header": "Este cabeçalho será aplicado a todas as requisições desta coleção.", "generate_documentation_first": "Gere a documentação primeiro", - "network_fail": "Incapaz de alcançar o endpoint da API. Verifique sua conexão de rede e tente novamente.", + "network_fail": "Não foi possível alcançar o endpoint da API. Verifique sua conexão de rede e tente novamente.", "offline": "Você parece estar offline. Os dados neste espaço de trabalho podem não estar atualizados.", "offline_short": "Você parece estar offline.", "post_request_tests": "Os scripts de teste são gravados em JavaScript e executados após o recebimento da resposta.", @@ -369,7 +419,7 @@ "tests": "Escreva um script de teste para automatizar a depuração." }, "hide": { - "collection": "Collapse Collection Panel", + "collection": "Encolher Painel de Coleções", "more": "Esconda mais", "preview": "Esconder a Antevisão", "sidebar": "Ocultar barra lateral" @@ -377,59 +427,64 @@ "import": { "collections": "Importar coleções", "curl": "Importar cURL", - "environments_from_gist": "Import From Gist", - "environments_from_gist_description": "Import Hoppscotch Environments From Gist", + "environments_from_gist": "Importar do Gist", + "environments_from_gist_description": "Importar Ambientes Hoppscotch do Gist", "failed": "A importação falhou", - "from_file": "Import from File", + "from_file": "Importar de Arquivo", "from_gist": "Importar do Gist", "from_gist_description": "Importar de URL Gist", "from_insomnia": "Importar de Insomnia", "from_insomnia_description": "Importa de coleção Insomnia", "from_json": "Importar de Hoppscotch", - "from_json_description": "Importa de arquivo de coleção Hoppscotch", + "from_json_description": "Importar de arquivo de coleção Hoppscotch", "from_my_collections": "Importar de minhas coleções", - "from_my_collections_description": "Importa de arquivo Minhas Coleções", + "from_my_collections_description": "Importar de arquivo Minhas Coleções", "from_openapi": "Importar de OpenAPI", "from_openapi_description": "Importa de arquivo de especificação OpenAPI (YML/JSON)", "from_postman": "Importar de Postman", - "from_postman_description": "Importa de coleção Postman", + "from_postman_description": "Importar de coleção Postman", "from_url": "Importar de URL", "gist_url": "Insira o URL do Gist", - "gql_collections_from_gist_description": "Import GraphQL Collections From Gist", - "hoppscotch_environment": "Hoppscotch Environment", - "hoppscotch_environment_description": "Import Hoppscotch Environment JSON file", - "import_from_url_invalid_fetch": "Couldn't get data from the url", - "import_from_url_invalid_file_format": "Error while importing collections", - "import_from_url_invalid_type": "Unsupported type. accepted values are 'hoppscotch', 'openapi', 'postman', 'insomnia'", - "import_from_url_success": "Collections Imported", - "insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file", + "gql_collections_from_gist_description": "Importar Coleções GraphQL do Gist", + "hoppscotch_environment": "Ambiente Hoppscotch", + "hoppscotch_environment_description": "Importar Ambiente Hoppscotch de arquivo JSON", + "import_from_url_invalid_fetch": "Não foi possível buscar os dados pela URL", + "import_from_url_invalid_file_format": "Erro ao importar as coleções", + "import_from_url_invalid_type": "Tipo não suportado. Valores aceitos: 'hoppscotch', 'openapi', 'postman', 'insomnia'", + "import_from_url_success": "Coleções importadas", + "insomnia_environment_description": "Importar Ambiente Insomnia de arquivo JSON/YAML", "json_description": "Importa coleções de um arquivo JSON de Coleções Hoppscotch", - "postman_environment": "Postman Environment", - "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Importar" + "postman_environment": "Ambiente Postman", + "postman_environment_description": "Importar Ambiente Postman de arquivo JSON/YAML", + "title": "Importar", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { - "description": "Inspect possible errors", + "description": "Inspecionar possíveis erros", "environment": { - "add_environment": "Add to Environment", - "not_found": "Environment variable “{environment}” not found." + "add_environment": "Adicionar ao Ambiente", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", + "not_found": "Variável de ambiente “{environment}” não encontrada." }, "header": { - "cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." + "cookie": "O navegador não permite que o Hoppscotch aplique o Cabeçalho Cookie. Enquanto trabalhamos no Hoppscotch Desktop (em breve), por favor use o Cabeçalho de Autorização." }, "response": { - "401_error": "Please check your authentication credentials.", - "404_error": "Please check your request URL and method type.", - "cors_error": "Please check your Cross-Origin Resource Sharing configuration.", - "default_error": "Please check your request.", - "network_error": "Please check your network connection." + "401_error": "Verifique suas credenciais de autenticação.", + "404_error": "Verifique a URL da requisição e o tipo de método.", + "cors_error": "Verifique sua configuração de CORS (Cross-Origin Resource Sharing)", + "default_error": "Por favor, verifique sua requisição.", + "network_error": "Por favor, verifique sua conexão de rede." }, - "title": "Inspector", + "title": "Inspetor", "url": { - "extension_not_installed": "Extension not installed.", + "extension_not_installed": "Extensão não instalada", "extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", - "extention_enable_action": "Enable Browser Extension", - "extention_not_enabled": "Extension not enabled." + "extention_enable_action": "Habilitar Extensão do Navegador", + "extention_not_enabled": "Extensão não habilitada." } }, "layout": { @@ -440,25 +495,25 @@ "row": "Layout horizontal" }, "modal": { - "close_unsaved_tab": "You have unsaved changes", + "close_unsaved_tab": "Você possui alterações não salvas", "collections": "Coleções", - "confirm": "confirme", - "customize_request": "Customize Request", + "confirm": "Confirme", + "customize_request": "Customizar Requisição", "edit_request": "Editar pedido", "import_export": "Importar / Exportar", - "share_request": "Share Request" + "share_request": "Compartilhar Requisição" }, "mqtt": { - "already_subscribed": "You are already subscribed to this topic.", + "already_subscribed": "Você já está inscrito neste tópico.", "clean_session": "Clean Session", "clear_input": "Clear input", "clear_input_on_send": "Clear input on send", "client_id": "Client ID", - "color": "Pick a color", + "color": "Selecione uma cor", "communication": "Comunicação", "connection_config": "Connection Config", - "connection_not_authorized": "This MQTT connection does not use any authentication.", - "invalid_topic": "Please provide a topic for the subscription", + "connection_not_authorized": "Esta conexão MQTT connection não usa nenhuma autenticação.", + "invalid_topic": "Por favor, informe um tópico para a inscrição", "keep_alive": "Keep Alive", "log": "Registro", "lw_message": "Last-Will Message", @@ -466,8 +521,8 @@ "lw_retain": "Last-Will Retain", "lw_topic": "Last-Will Topic", "message": "Mensagem", - "new": "New Subscription", - "not_connected": "Please start a MQTT connection first.", + "new": "Nova Inscrição", + "not_connected": "Por favor, inicie uma conexão MQTT primeiro.", "publish": "Publicar", "qos": "QoS", "ssl": "SSL", @@ -475,13 +530,13 @@ "topic": "Tema", "topic_name": "Nome do tópico", "topic_title": "Publicar / Assinar tópico", - "unsubscribe": "Cancelar subscrição", + "unsubscribe": "Cancelar inscrição", "url": "URL" }, "navigation": { "doc": "Docs", "graphql": "GraphQL", - "profile": "Profile", + "profile": "Perfil", "realtime": "Tempo real", "rest": "REST", "settings": "Configurações" @@ -493,8 +548,8 @@ "snippets": "Trechos" }, "profile": { - "app_settings": "App Settings", - "default_hopp_displayname": "Unnamed User", + "app_settings": "Configurações do App", + "default_hopp_displayname": "Usuário Desconhecido", "editor": "Editor", "editor_description": "Editores podem adicionar, editar e deletar requisições.", "email_verification_mail": "Um e-mail de verificação foi enviado ao seu endereço de e-mail. Por favor, clique no link para verificar seu endereço e-mail.", @@ -517,25 +572,25 @@ "choose_language": "Escolha o seu idioma", "content_type": "Tipo de conteúdo", "content_type_titles": { - "others": "Others", - "structured": "Structured", - "text": "Text" + "others": "Outros", + "structured": "Estruturado", + "text": "Texto" }, - "different_collection": "Cannot reorder requests from different collections", - "duplicated": "Request duplicated", + "different_collection": "Não é possível reordenar requisições de coleções diferentes", + "duplicated": "Requisição duplicada", "duration": "Duração", - "enter_curl": "Digite cURL", + "enter_curl": "Digite o cURL", "generate_code": "Gerar código", "generated_code": "Código gerado", - "go_to_authorization_tab": "Go to Authorization tab", - "go_to_body_tab": "Go to Body tab", + "go_to_authorization_tab": "Ir para a aba de Autorização", + "go_to_body_tab": "Ir para a aba de Corpo", "header_list": "Lista de Cabeçalhos", "invalid_name": "Forneça um nome para a requisição", "method": "Método", - "moved": "Request moved", + "moved": "Requisição movida", "name": "Nome da requisição", "new": "Nova requisição", - "order_changed": "Request Order Updated", + "order_changed": "Ordem de Requisições Atualizada", "override": "Substituir", "override_help": "Substituir Content-Type em Headers", "overriden": "Substituído", @@ -545,8 +600,9 @@ "payload": "Payload", "query": "Enviar", "raw_body": "Corpo de Requisição Bruta", - "rename": "Rename Request", + "rename": "Renomear Requisição", "renamed": "Requisição renomeada", + "request_variables": "Request variables", "run": "Executar", "save": "Salvar", "save_as": "Salvar como", @@ -558,6 +614,7 @@ "title": "Solicitar", "type": "Tipo de requisição", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Variáveis", "view_my_links": "View my links", "copy_link": "Link de cópia" @@ -565,7 +622,7 @@ "response": { "audio": "Audio", "body": "Corpo de Resposta", - "filter_response_body": "Filter JSON response body (uses JSONPath syntax)", + "filter_response_body": "Filtar corpo de resposta JSON (utiliza sintaxe JSONPath)", "headers": "Cabeçalhos", "html": "HTML", "image": "Imagem", @@ -584,17 +641,17 @@ "settings": { "accent_color": "Cor de destaque", "account": "Conta", - "account_deleted": "Your account has been deleted", + "account_deleted": "Sua conta foi excluída", "account_description": "Personalize as configurações da sua conta.", "account_email_description": "Seu endereço de e-mail principal.", "account_name_description": "Este é o seu nome de exibição.", - "additional": "Additional Settings", + "additional": "Configurações adicionais", "background": "Fundo", "black_mode": "Preto", "choose_language": "Escolha o seu idioma", "dark_mode": "Escuro", - "delete_account": "Delete account", - "delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.", + "delete_account": "Excluir conta", + "delete_account_description": "Ao deletar sua conta, todos os seus dados serão permanentemente excluídos. Essa ação não pode ser desfeita.", "expand_navigation": "Expandir navegação", "experiments": "Experimentos", "experiments_notice": "Esta é uma coleção de experimentos em que estamos trabalhando que podem ser úteis, divertidos, ambos ou nenhum dos dois. Eles não são finais e podem não ser estáveis, portanto, se algo muito estranho acontecer, não entre em pânico. Apenas desligue essa maldita coisa. Piadas à parte,", @@ -618,7 +675,7 @@ "read_the": "Leia o", "reset_default": "Restaurar ao padrão", "short_codes": "Short codes", - "short_codes_description": "Short codes which were created by you.", + "short_codes_description": "Short codes que foram criados por você.", "sidebar_on_left": "Barra lateral à esquerda", "sync": "Sincronizar", "sync_collections": "Coleções", @@ -632,33 +689,33 @@ "theme_description": "Personalize o tema do seu aplicativo.", "use_experimental_url_bar": "Use a barra de URL experimental com destaque do ambiente", "user": "Do utilizador", - "verified_email": "Verified email", + "verified_email": "Email verificado", "verify_email": "Verificar email" }, "shared_requests": { - "button": "Button", - "button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.", - "copy_html": "Copy HTML", - "copy_link": "Copy Link", - "copy_markdown": "Copy Markdown", - "creating_widget": "Creating widget", - "customize": "Customize", - "deleted": "Shared request deleted", - "description": "Select a widget, you can change and customize this later", - "embed": "Embed", - "embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.", + "button": "Botão", + "button_info": "Ciar um botão 'Executar no Hoppscotch' para seu website, blog ou README.", + "copy_html": "Copiar HTML", + "copy_link": "Copiar Link", + "copy_markdown": "Copiar Markdown", + "creating_widget": "Criando widget", + "customize": "Personalizar", + "deleted": "Requisição compartilhada excluída", + "description": "Selecione um widget, você poderá mudar e personalizar isso depois", + "embed": "Incorporar", + "embed_info": "Adicionar um mini 'Hoppscotch API Playground' a seus website, blog ou documentação.", "link": "Link", - "link_info": "Create a shareable link to share with anyone on the internet with view access.", - "modified": "Shared request modified", - "not_found": "Shared request not found", - "open_new_tab": "Open in new tab", - "preview": "Preview", - "run_in_hoppscotch": "Run in Hoppscotch", + "link_info": "Crie um link compartilhável para compartilhar com qualquer pessoa na internet com acesso de visualização.", + "modified": "Requisição compartilhada modificada", + "not_found": "Requisição compartilhada não encontrada", + "open_new_tab": "Abrir em nova aba", + "preview": "Pré-visualizar", + "run_in_hoppscotch": "Executar no Hoppscotch", "theme": { - "dark": "Dark", - "light": "Light", - "system": "System", - "title": "Theme" + "dark": "Escuro", + "light": "Claro", + "system": "Sistema", + "title": "Tema" } }, "shortcut": { @@ -698,20 +755,20 @@ "post_method": "Selecione o método POST", "previous_method": "Selecione o método anterior", "put_method": "Selecione o método PUT", - "rename": "Rename Request", + "rename": "Renomear Requisição", "reset_request": "Pedido de reinicialização", - "save_request": "Save Request", + "save_request": "Salvar Requisição", "save_to_collections": "Salvar em coleções", "send_request": "Enviar requisição", - "share_request": "Share Request", - "show_code": "Generate code snippet", + "share_request": "Compartilhar Requisição", + "show_code": "Gerar snippet de código", "title": "Solicitar", "copy_request_link": "Copiar link de requisição" }, "response": { - "copy": "Copy response to clipboard", - "download": "Download response as file", - "title": "Response" + "copy": "Copiar reposta para a área de transferência", + "download": "Baixar resposta como arquivo", + "title": "Resposta" }, "theme": { "black": "Trocar para o tema preto", @@ -723,95 +780,102 @@ }, "show": { "code": "Exibir código", - "collection": "Expand Collection Panel", + "collection": "Expandir Painel de Coleções", "more": "Mostre mais", "sidebar": "Mostrar barra lateral" }, "socketio": { "communication": "Comunicação", - "connection_not_authorized": "This SocketIO connection does not use any authentication.", + "connection_not_authorized": "Esta conexão SocketIO connection não usa nenhuma autenticação.", "event_name": "Nome do evento", "events": "Eventos", "log": "Registro", "url": "URL" }, "spotlight": { - "change_language": "Change Language", + "change_language": "Mudar idioma", "environments": { - "delete": "Delete current environment", - "duplicate": "Duplicate current environment", - "duplicate_global": "Duplicate global environment", - "edit": "Edit current environment", - "edit_global": "Edit global environment", - "new": "Create new environment", - "new_variable": "Create a new environment variable", - "title": "Environments" + "delete": "Excluir ambiente atual", + "duplicate": "Duplicar ambiente atual", + "duplicate_global": "Duplicate ambiente global", + "edit": "Editar ambiente atual", + "edit_global": "Editar ambiente global", + "new": "Criar novo ambiente", + "new_variable": "Cirar nova variável de ambiente", + "title": "Ambientes" }, "general": { - "chat": "Chat with support", - "help_menu": "Help and support", - "open_docs": "Read Documentation", - "open_github": "Open GitHub repository", - "open_keybindings": "Keyboard shortcuts", + "chat": "Conversar com o suporte", + "help_menu": "Ajuda e suporte", + "open_docs": "Ler a Documentação", + "open_github": "Abrir repositório GitHub", + "open_keybindings": "Atalhos de teclado", "social": "Social", - "title": "General" + "title": "Geral" }, "graphql": { - "connect": "Connect to server", - "disconnect": "Disconnect from server" + "connect": "Conectar ao servidor", + "disconnect": "Desconectar do servidor" }, "miscellaneous": { - "invite": "Invite your friends to Hoppscotch", - "title": "Miscellaneous" + "invite": "Convide amigos para o Hoppscotch", + "title": "Diversos" }, "request": { - "save_as_new": "Save as new request", - "select_method": "Select method", - "switch_to": "Switch to", - "tab_authorization": "Authorization tab", - "tab_body": "Body tab", - "tab_headers": "Headers tab", - "tab_parameters": "Parameters tab", - "tab_pre_request_script": "Pre-request script tab", - "tab_query": "Query tab", - "tab_tests": "Tests tab", - "tab_variables": "Variables tab" + "save_as_new": "Salvar como nova requisição", + "select_method": "Selecionar método", + "switch_to": "Alternar para", + "tab_authorization": "Aba de Autorização", + "tab_body": "Aba de Corpo", + "tab_headers": "Aba de Cabeçalhos", + "tab_parameters": "Aba de Parâmetros", + "tab_pre_request_script": "Aba de script pré-requisição", + "tab_query": "Aba de Query", + "tab_tests": "Aba de Testes", + "tab_variables": "Aba de Variáveis" }, "response": { - "copy": "Copy response", - "download": "Download response as file", - "title": "Response" + "copy": "Copiar resposta", + "download": "Baixar resposta como arquivo", + "title": "Resposta" }, "section": { "interceptor": "Interceptor", "interface": "Interface", - "theme": "Theme", - "user": "User" + "theme": "Tema", + "user": "Usuário" }, "settings": { - "change_interceptor": "Change Interceptor", - "change_language": "Change Language", + "change_interceptor": "Altrar Interceptor", + "change_language": "Alterar Idioma", "theme": { - "black": "Black", - "dark": "Dark", - "light": "Light", - "system": "System preference" + "black": "Preto", + "dark": "Escuro", + "light": "Claro", + "system": "Configuração do sistema" } }, "tab": { - "close_current": "Close current tab", - "close_others": "Close all other tabs", - "duplicate": "Duplicate current tab", - "new_tab": "Open a new tab", - "title": "Tabs" + "close_current": "Fechar a aba atual", + "close_others": "Fechar as outras abas", + "duplicate": "Duplicar a aba atual", + "new_tab": "Abrir uma nova aba", + "title": "Abas" }, "workspace": { - "delete": "Delete current team", - "edit": "Edit current team", - "invite": "Invite people to team", - "new": "Create new team", - "switch_to_personal": "Switch to your personal workspace", - "title": "Teams" + "delete": "Excluir equipe atual", + "edit": "Editar equipe atual", + "invite": "Convidar para a equipe", + "new": "Criar nova equipe", + "switch_to_personal": "Alternar para seu Workspace pessoal", + "title": "Equipes" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -826,10 +890,10 @@ "connected": "Conectado", "connected_to": "Conectado a {name}", "connecting_to": "Conectando-se a {name} ...", - "connection_error": "Failed to connect", - "connection_failed": "Connection failed", - "connection_lost": "Connection lost", - "copied_interface_to_clipboard": "Copied {language} interface type to clipboard", + "connection_error": "Falha ao conectar", + "connection_failed": "Falha na conexão", + "connection_lost": "Conexão perdida", + "copied_interface_to_clipboard": "Interface {language} copiada para a área de transferência", "copied_to_clipboard": "Copiado para a área de transferência", "deleted": "Excluído", "deprecated": "DESCONTINUADA", @@ -837,28 +901,28 @@ "disconnected": "Desconectado", "disconnected_from": "Desconectado de {name}", "docs_generated": "Documentação gerada", - "download_failed": "Download failed", + "download_failed": "Download falhou", "download_started": "Download iniciado", "enabled": "Habilitado", "file_imported": "Arquivo importado", "finished_in": "Terminado em {duration} ms", - "hide": "Hide", + "hide": "Ocultar", "history_deleted": "Histórico excluído", "linewrap": "Quebrar linhas", "loading": "Carregando...", - "message_received": "Message: {message} arrived on topic: {topic}", - "mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}", + "message_received": "Mensagem: {message} recebida no tópico: {topic}", + "mqtt_subscription_failed": "Ao deu errado ao inscrever-se no tópico: {topic}", "none": "Nenhum", "nothing_found": "Nada encontrado para", "published_error": "Something went wrong while publishing msg: {topic} to topic: {message}", "published_message": "Published message: {message} to topic: {topic}", - "reconnection_error": "Failed to reconnect", + "reconnection_error": "Falha ao reconectar", "show": "Show", - "subscribed_failed": "Failed to subscribe to topic: {topic}", - "subscribed_success": "Successfully subscribed to topic: {topic}", - "unsubscribed_failed": "Failed to unsubscribe from topic: {topic}", - "unsubscribed_success": "Successfully unsubscribed from topic: {topic}", - "waiting_send_request": "Esperando para enviar pedido" + "subscribed_failed": "Faha ao inscrever-se no tópico: {topic}", + "subscribed_success": "Inscrito com sucesso no tópico: {topic}", + "unsubscribed_failed": "Falha ao cancelar inscrição no tópico: {topic}", + "unsubscribed_success": "Inscrição cancelada no tópico: {topic}", + "waiting_send_request": "Esperando para enviar requisição" }, "support": { "changelog": "Leia mais sobre os últimos lançamentos", @@ -866,30 +930,33 @@ "community": "Faça perguntas e ajude os outros", "documentation": "Leia mais sobre Hoppscotch", "forum": "Faça perguntas e obtenha respostas", - "github": "Follow us on Github", + "github": "Siga-nos no Github", "shortcuts": "Navegue pelo aplicativo mais rápido", - "team": "Entre em contato com a equipe", "title": "Apoio, suporte", - "twitter": "Siga-nos no Twitter" + "twitter": "Siga-nos no Twitter", + "team": "Entre em contato com a equipe" }, "tab": { "authorization": "Autorização", "body": "Corpo", - "close": "Close Tab", - "close_others": "Close other Tabs", + "close": "Fechar aba", + "close_others": "Fechar outras abas", "collections": "Coleções", "documentation": "Documentação", - "duplicate": "Duplicate Tab", - "environments": "Environments", + "duplicate": "Duplicar aba", + "environments": "Ambientes", "headers": "Cabeçalhos", "history": "História", "mqtt": "MQTT", "parameters": "Parâmetros", - "pre_request_script": "Script de pré-solicitação", + "pre_request_script": "Script de pré-requisição", "queries": "Consultas", "query": "Consulta", "schema": "Schema", - "shared_requests": "Shared Requests", + "shared_requests": "Requisições compartilhadas", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Testes", @@ -906,8 +973,8 @@ "email_do_not_match": "O e-mail não corresponde aos detalhes da sua conta. Contate o dono da sua equipe.", "exit": "Sair da equipe", "exit_disabled": "Apenas o dono não pode sair da equipe", - "failed_invites": "Failed invites", - "invalid_coll_id": "Invalid collection ID", + "failed_invites": "Convites com falha", + "invalid_coll_id": "ID de Coleção inválido", "invalid_email_format": "O formato do email é inválido", "invalid_id": "ID de time inválido. Contate o dono da sua equipe.", "invalid_invite_link": "Link de convite inválido", @@ -918,7 +985,6 @@ "invite_tooltip": "Convidar pessoas para esse workspace", "invited_to_team": "{owner} te convidou para se juntar ao {team}", "join": "Convite aceito", - "join_beta": "Junte-se ao programa beta para acessar as equipes.", "join_team": "Junte-se ao {team}", "joined_team": "Você se juntou ao {team}", "joined_team_description": "Você agora é um membro deste time", @@ -931,32 +997,37 @@ "member_removed": "Usuário removido", "member_role_updated": "Funções de usuário atualizadas", "members": "Membros", - "more_members": "+{count} more", + "more_members": "+{count} mais", "name_length_insufficient": "O nome da equipe deve ter pelo menos 6 caracteres", - "name_updated": "Nome do time atualizado", - "new": "Novo time", + "name_updated": "Nome da equipe atualizado", + "new": "Nova equipe", "new_created": "Nova equipe criada", "new_name": "Minha Nova Equipe", "no_access": "Você não tem acesso de edição a essas coleções", "no_invite_found": "Convite não encontrado. Contate o dono da sua equipe.", - "no_request_found": "Request not found.", + "no_request_found": "Requisição não encontrada.", "not_found": "Equipe não encontrada. Contate o dono da sua equipe", - "not_valid_viewer": "Você não é um visualizado válido. Contate o dono da sua equipe.", - "parent_coll_move": "Cannot move collection to a child collection", + "not_valid_viewer": "Você não é um visualizador válido. Contate o dono da sua equipe.", + "parent_coll_move": "Não é possível mover a coleção para uma coleção filha.", "pending_invites": "Convites pendentes", "permissions": "Permissões", - "same_target_destination": "Same target and destination", + "same_target_destination": "A origem e o destino são iguais", "saved": "Equipe salva", "select_a_team": "Selecione uma equipe", - "success_invites": "Success invites", + "success_invites": "Convites com sucesso", "title": "Equipes", "we_sent_invite_link": "Link de convite enviado para todos os convidados!", - "we_sent_invite_link_description": "Peça a todos os convidados que verifiquem sua caixa de entrada. Clique no link para se juntar à equipe." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Peça a todos os convidados que verifiquem sua caixa de entrada. Clique no link para se juntar à equipe.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Junte-se ao programa beta para acessar as equipes." }, "team_environment": { - "deleted": "Environment Deleted", - "duplicate": "Environment Duplicated", - "not_found": "Environment not found." + "deleted": "Ambiente Excluído", + "duplicate": "Ambiente Duplicado", + "not_found": "Ambiente não encontrado." }, "test": { "failed": "teste falhou", @@ -976,17 +1047,58 @@ "url": "URL" }, "workspace": { - "change": "Change workspace", - "personal": "My Workspace", - "team": "Team Workspace", + "change": "Alterar Workspace", + "personal": "Meu Workspace", + "other_workspaces": "My Workspaces", + "team": "Workspace da Equipe", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { - "actions": "Actions", - "created_on": "Created on", - "deleted": "Shortcode deleted", + "actions": "Ações", + "created_on": "Criado em", + "deleted": "Shortcode excluído", "method": "Method", - "not_found": "Shortcode not found", + "not_found": "Shortcode não encontrado", "short_code": "Short code", "url": "URL" } diff --git a/packages/hoppscotch-common/locales/pt.json b/packages/hoppscotch-common/locales/pt.json index 8fc3aa2cdb..96b429ad05 100644 --- a/packages/hoppscotch-common/locales/pt.json +++ b/packages/hoppscotch-common/locales/pt.json @@ -24,8 +24,10 @@ "go_back": "Volte", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Etiqueta", "learn_more": "Saber mais", + "download_here": "Download here", "less": "Less", "more": "Mais", "new": "Novo", @@ -43,6 +45,7 @@ "search": "Procurar", "send": "Mandar", "share": "Share", + "show_secret": "Show secret", "start": "Começar", "starting": "Starting", "stop": "Pare", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Digite um comando ou pesquise ...", "we_use_cookies": "Usamos cookies", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "O que há de novo?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "A conta existe com credenciais diferentes - Faça login para vincular as duas contas", "all_sign_in_options": "Todas as opções de login", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Continue com Email", "continue_with_github": "Continue com GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Continue com o Google", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "Continue com OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Senha", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Símbolo", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Editar coleção", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Forneça um nome válido para a coleção", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Selecione uma coleção", "select_location": "Selecione a localização", + "details": "Details", "select_team": "Selecione uma equipe", "team_collections": "Coleções da equipe" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Tem certeza de que deseja cancelar a telemetria?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Tem certeza de que deseja sincronizar este espaço de trabalho?" + "sync": "Tem certeza de que deseja sincronizar este espaço de trabalho?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "There are no pending invites for this team", "profile": "Login to view your profile", "protocols": "Os protocolos estão vazios", + "request_variables": "This request does not have any request variables", "schema": "Conecte-se a um endpoint GraphQL", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Nome do time vazio", "teams": "Times estão vazios", "tests": "Não há testes para esta solicitação", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Selecione o ambiente", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment updation", "value": "Value", "variable": "Variable", - "variable_list": "Lista de Variáveis" + "variables": "Variables", + "variable_list": "Lista de Variáveis", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Nome do pedido vazio", "f12_details": "(F12 para detalhes)", "gql_prettify_invalid_query": "Não foi possível justificar uma consulta inválida, resolva os erros de sintaxe da consulta e tente novamente", @@ -297,6 +337,7 @@ "incorrect_email": "Incorrect email", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "Não foi possível embelezar um corpo inválido, resolver erros de sintaxe json e tentar novamente", "network_error": "There seems to be a network error. Please try again.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Não foi possível executar o script de pré-solicitação", "something_went_wrong": "Algo deu errado", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Exportar como JSON", "create_secret_gist": "Crie uma essência secreta", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist criado", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Faça login com GitHub para criar uma essência secreta", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Gist criado" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutações", "schema": "Esquema", "subscriptions": "Assinaturas", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Importar" + "title": "Importar", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Corpo de Solicitação Bruta", "rename": "Rename Request", "renamed": "Pedido renomeado", + "request_variables": "Request variables", "run": "Corre", "save": "Salvar", "save_as": "Salvar como", @@ -558,6 +614,7 @@ "title": "Solicitar", "type": "Tipo de solicitação", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Variáveis", "view_my_links": "View my links", "copy_link": "Link de cópia" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Faça perguntas e obtenha respostas", "github": "Follow us on Github", "shortcuts": "Navegue pelo aplicativo mais rápido", - "team": "Entre em contato com a equipe", "title": "Apoio, suporte", - "twitter": "Siga-nos no Twitter" + "twitter": "Siga-nos no Twitter", + "team": "Entre em contato com a equipe" }, "tab": { "authorization": "Autorização", @@ -890,6 +954,9 @@ "query": "Consulta", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Testes", @@ -918,7 +985,6 @@ "invite_tooltip": "Invite people to this workspace", "invited_to_team": "{owner} invited you to join {team}", "join": "Invitation accepted", - "join_beta": "Junte-se ao programa beta para acessar as equipes.", "join_team": "Join {team}", "joined_team": "You have joined {team}", "joined_team_description": "You are now a member of this team", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Times", "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Junte-se ao programa beta para acessar as equipes." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/ro.json b/packages/hoppscotch-common/locales/ro.json index 4d26dee95b..c4ab2d18f5 100644 --- a/packages/hoppscotch-common/locales/ro.json +++ b/packages/hoppscotch-common/locales/ro.json @@ -24,8 +24,10 @@ "go_back": "Înapoi", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Etichetă", "learn_more": "Află mai multe", + "download_here": "Download here", "less": "Mai puțin", "more": "Mai mult", "new": "Nou", @@ -43,6 +45,7 @@ "search": "Căutare", "send": "Trimite", "share": "Share", + "show_secret": "Show secret", "start": "Start", "starting": "Starting", "stop": "Stop", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Tastați o comandă sau căutați ...", "we_use_cookies": "Folosim cookie-uri", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Ce mai e nou?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Contul există cu credențiale diferite - Conectați-vă pentru a conecta ambele conturi", "all_sign_in_options": "Toate opțiunile de conectare", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Continuați cu e-mailul", "continue_with_github": "Continuați cu GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Continuați cu Google", "continue_with_microsoft": "Continuați cu Microsoft", "continue_with_oidc": "Continuați cu OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Transimteți cheia", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Parolă", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Token", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Editați colecția", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Vă rugăm să furnizați un nume valid pentru colecție", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Selectați o colecție", "select_location": "Selectați locația", + "details": "Details", "select_team": "Selectați o echipă", "team_collections": "Colecții de echipă" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Sigur doriți să renunțați la telemetrie?", "request_change": "Sigur doriți să renunțați la cererea curentă? Modificările nesalvate se vor pierde.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Sigur doriți să sincronizați acest spațiu de lucru?" + "sync": "Sigur doriți să sincronizați acest spațiu de lucru?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "Nu sunt invitații în așteptare pentru această echipă", "profile": "Autentificați-vă pentru a putea vizualiza profilul", "protocols": "Protocoalele sunt goale", + "request_variables": "This request does not have any request variables", "schema": "Conectați-vă la un Endpoint GraphQL", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Numele echipei este gol", "teams": "Echipele sunt goale", "tests": "Nu există teste pentru această solicitare", + "access_tokens": "Access tokens are empty", "shortcodes": "Codurile scurte sunt goale" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Selectați mediul", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Mediu actualizat", "value": "Value", "variable": "Variable", - "variable_list": "Lista variabilelor" + "variables": "Variables", + "variable_list": "Lista variabilelor", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Nume cerere goală", "f12_details": "(F12 pentru detalii)", "gql_prettify_invalid_query": "Nu am putut formata o interogare nevalidă, rezolvați erorile de sintaxă ale interogării și încercați din nou", @@ -297,6 +337,7 @@ "incorrect_email": "Email incorect", "invalid_link": "Link invalid", "invalid_link_description": "Link-ul este invalid sau a expirat.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "JSON invalid", "json_prettify_invalid_body": "Nu s-a putut formata un corp invalid, rezolvați erorile de sintaxă JSON și încercați din nou", "network_error": "Probleme cu conexiunea. Încercați din nou.", @@ -308,17 +349,25 @@ "page_not_found": "Pagina nu a putut fi găsită", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Nu s-a putut executa scriptul", "something_went_wrong": "Ceva nu a mers bine", - "test_script_fail": "Nu s-a putut executa scriptul" + "test_script_fail": "Nu s-a putut executa scriptul", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Exportați ca JSON", "create_secret_gist": "Creați Gist secret", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist creat", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Conectați-vă cu GitHub pentru a crea un Gist secret", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Gist creat" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutații", "schema": "Schemă", "subscriptions": "Abonamente", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Importați colecții dintr-un fisier de collectii JSON Hoppscotch", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Import" + "title": "Import", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Corpul cererii", "rename": "Rename Request", "renamed": "Cerere redenumită", + "request_variables": "Request variables", "run": "Execută", "save": "Salvează", "save_as": "Salvează ca", @@ -558,6 +614,7 @@ "title": "Cerere", "type": "Tip de cerere", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Variabile", "view_my_links": "Vizualizare link-uri", "copy_link": "Copiază legătură" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Puneți întrebări și primiți răspunsuri", "github": "Urmarește-ne pe Github", "shortcuts": "Utilizați aplicația mai rapid", - "team": "Luați legătura cu echipa", "title": "Suport", - "twitter": "Urmăriți-ne pe Twitter" + "twitter": "Urmăriți-ne pe Twitter", + "team": "Luați legătura cu echipa" }, "tab": { "authorization": "Autorizare", @@ -890,6 +954,9 @@ "query": "Interogare", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Teste", @@ -918,7 +985,6 @@ "invite_tooltip": "Invită oameni la acest workspace", "invited_to_team": "{owner} te-a invitat să te alături echipei {team}", "join": "Invitație acceptată", - "join_beta": "Alăturați-vă programului beta pentru a accesa echipe.", "join_team": "Alăturați-vă {team}", "joined_team": "V-ați alăturat echipei {team}", "joined_team_description": "Sunteți acum membru al acestei echipe", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Echipe", "we_sent_invite_link": "Am trimis un link de invitație către toți invitații!", - "we_sent_invite_link_description": "Rugați toate persoanele invitate să iși verifice căsuța. Click pe link pentru a se alătura echipei." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Rugați toate persoanele invitate să iși verifice căsuța. Click pe link pentru a se alătura echipei.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Alăturați-vă programului beta pentru a accesa echipe." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Acțiuni", "created_on": "Creat la", diff --git a/packages/hoppscotch-common/locales/ru.json b/packages/hoppscotch-common/locales/ru.json index 1c7661acb7..10e4fd983c 100644 --- a/packages/hoppscotch-common/locales/ru.json +++ b/packages/hoppscotch-common/locales/ru.json @@ -11,7 +11,7 @@ "connect": "Подключиться", "connecting": "Соединение...", "copy": "Скопировать", - "create": "Create", + "create": "Создать", "delete": "Удалить", "disconnect": "Отключиться", "dismiss": "Скрыть", @@ -24,16 +24,18 @@ "go_back": "Вернуться", "go_forward": "Вперёд", "group_by": "Сгруппировать по", + "hide_secret": "Спрятать секрет", "label": "Название", "learn_more": "Узнать больше", + "download_here": "Download here", "less": "Меньше", "more": "Больше", - "new": "Создать новый", + "new": "Создать", "no": "Нет", "open_workspace": "Открыть пространство", "paste": "Вставить", "prettify": "Форматировать", - "properties": "Properties", + "properties": "Параметры", "remove": "Удалить", "rename": "Переименовать", "restore": "Восстановить", @@ -42,20 +44,21 @@ "scroll_to_top": "Вверх", "search": "Поиск", "send": "Отправить", - "share": "Share", + "share": "Поделиться", + "show_secret": "Показать секрет", "start": "Начать", "starting": "Запускаю", "stop": "Стоп", - "to_close": "что бы закрыть", + "to_close": "закрыть", "to_navigate": "для навигации", - "to_select": "выборать", + "to_select": "выбрать", "turn_off": "Выключить", "turn_on": "Включить", "undo": "Отменить", "yes": "Да" }, "add": { - "new": "Добавить новое", + "new": "Добавить", "star": "Добавить в избранное" }, "app": { @@ -66,26 +69,26 @@ "copy_interface_type": "Copy interface type", "copy_user_id": "Копировать токен пользователя", "developer_option": "Настройки разработчика", - "developer_option_description": "Инструмент разработчика помогает обслуживить и развивить Hoppscotch", + "developer_option_description": "Инструмент разработчика помогает обслуживать и развивать Hoppscotch", "discord": "Discord", "documentation": "Документация", "github": "GitHub", "help": "Справка, отзывы и документация", - "home": "Дом", + "home": "На главную", "invite": "Пригласить", "invite_description": "В Hoppscotch мы разработали простой и интуитивно понятный интерфейс для создания и управления вашими API. Hoppscotch - это инструмент, который помогает создавать, тестировать, документировать и делиться своими API.", "invite_your_friends": "Пригласить своих друзей", "join_discord_community": "Присоединяйтесь к нашему сообществу Discord", "keyboard_shortcuts": "Горячие клавиши", "name": "Hoppscotch", - "new_version_found": "Найдена новая версия. Перезагрузите для обновления.", - "open_in_hoppscotch": "Open in Hoppscotch", + "new_version_found": "Найдена новая версия. Перезагрузите для обновления. Все данные уже сохранены, ничего не потеряется", + "open_in_hoppscotch": "Открыть в Hoppscotch", "options": "Настройки", "proxy_privacy_policy": "Политика конфиденциальности прокси", "reload": "Перезагрузить", "search": "Поиск", "share": "Поделиться", - "shortcuts": "Ярлыки", + "shortcuts": "Горячие клавиши", "social_description": "Подписывайся на наши соц. сети и оставайся всегда в курсе последних новостей, обновлений и релизов.", "social_links": "Социальные сети", "spotlight": "Прожектор", @@ -95,19 +98,23 @@ "twitter": "Twitter", "type_a_command_search": "Введите команду или выполните поиск…", "we_use_cookies": "Мы используем куки", + "updated_text": "Hoppscotch был обновлен до v{version} 🎉", "whats_new": "Что нового?", - "wiki": "Вики" + "see_whats_new": "Узнать что нового", + "wiki": "Узнать больше" }, "auth": { "account_exists": "Учетная запись существует с разными учетными данными - войдите, чтобы связать обе учетные записи", "all_sign_in_options": "Все варианты входа", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Продолжить с электронной почтой", "continue_with_github": "Продолжить с GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Продолжить с Google", "continue_with_microsoft": "Продолжить с Microsoft", "continue_with_oidc": "Продолжить с OIDC", - "email": "Электронное письмо", - "logged_out": "Вышли из", + "email": "Электронная почта", + "logged_out": "Успешно вышли. Будем скучать!", "login": "Авторизоваться", "login_success": "Успешный вход в систему", "login_to_hoppscotch": "Войти в Hoppscotch", @@ -122,7 +129,7 @@ "generate_token": "Сгенерировать токен", "graphql_headers": "Authorization Headers are sent as part of the payload to connection_init", "include_in_url": "Добавить в URL", - "inherited_from": "Inherited {auth} from parent collection {collection} ", + "inherited_from": "Унаследован тип аутентификации {auth} из родительской коллекции \"{collection}\"", "learn": "Узнать больше", "oauth": { "redirect_auth_server_returned_error": "Auth Server returned an error state", @@ -135,12 +142,33 @@ "redirect_no_code_verifier": "No Code Verifier Defined", "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", - "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "something_went_wrong_on_token_generation": "Что-то пошло не так в процессе генерации токена доступа", + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, + "pass_by_headers_label": "Headers", + "pass_by_query_params_label": "Query Parameters", "pass_key_by": "Pass by", "password": "Пароль", - "save_to_inherit": "Please save this request in any collection to inherit the authorization", + "save_to_inherit": "Cохраните этот запрос в любой коллекции, чтобы унаследовать авторизацию", "token": "Токен", "type": "Метод авторизации", "username": "Имя пользователя" @@ -150,46 +178,47 @@ "different_parent": "Нельзя сортировать коллекцию с разной родительской коллекцией", "edit": "Редактировать коллекцию", "import_or_create": "Вы можете импортировать существующую или создать новую коллекцию", + "import_collection": "Импортировать коллекцию", "invalid_name": "Укажите допустимое название коллекции", "invalid_root_move": "Коллекция уже в корне", "moved": "Перемещено успешно", "my_collections": "Мои коллекции", "name": "Новая коллекция", - "name_length_insufficient": "Имя коллекции должно иметь 3 или более символов", + "name_length_insufficient": "Имя коллекции должно составлять не менее 3 символов", "new": "Создать коллекцию", "order_changed": "Порядок коллекции обновлён", - "properties": "Collection Properties", - "properties_updated": "Collection Properties Updated", + "properties": "Параметры коллекции", + "properties_updated": "Параметры коллекции обновлены", "renamed": "Коллекция переименована", "request_in_use": "Запрос обрабатывается", "save_as": "Сохранить как", "save_to_collection": "Сохранить в коллекцию", "select": "Выбрать коллекцию", "select_location": "Выберите местоположение", - "select_team": "Выберите команду", - "team_collections": "Коллекции команд" + "details": "Подробности" }, "confirm": { - "close_unsaved_tab": "Вы уверены что хотите закрыть эту вкладку?", - "close_unsaved_tabs": "Вы уверены что хотите закрыть все эти вкладки? Несохранённые данные {count} вкладок будут утеряны.", + "close_unsaved_tab": "Вы уверены, что хотите закрыть эту вкладку?", + "close_unsaved_tabs": "Вы уверены, что хотите закрыть все эти вкладки? Несохранённые данные {count} вкладок будут утеряны.", "exit_team": "Вы точно хотите покинуть эту команду?", "logout": "Вы действительно хотите выйти?", "remove_collection": "Вы уверены, что хотите навсегда удалить эту коллекцию?", - "remove_environment": "Вы действительно хотите удалить эту среду без возможности восстановления?", + "remove_environment": "Вы действительно хотите удалить это окружение без возможности восстановления?", "remove_folder": "Вы уверены, что хотите навсегда удалить эту папку?", "remove_history": "Вы уверены, что хотите навсегда удалить всю историю?", "remove_request": "Вы уверены, что хотите навсегда удалить этот запрос?", - "remove_shared_request": "Are you sure you want to permanently delete this shared request?", + "remove_shared_request": "Вы уверены, что хотите навсегда удалить этот запрос?", "remove_team": "Вы уверены, что хотите удалить эту команду?", "remove_telemetry": "Вы действительно хотите отказаться от телеметрии?", - "request_change": "Вы уверены что хотите сбросить текущий запрос, все не сохранённые данные будт утеряны?", + "request_change": "Вы уверены, что хотите сбросить текущий запрос, все несохранённые данные будт утеряны?", "save_unsaved_tab": "Вы хотите сохранить изменения в этой вкладке?", - "sync": "Вы уверены, что хотите синхронизировать это рабочее пространство?" + "sync": "Вы уверены, что хотите синхронизировать это рабочее пространство?", + "delete_access_token": "Вы уверены, что хотите удалить токен доступа {tokenLabel}?" }, "context_menu": { - "add_parameters": "Add to parameters", - "open_request_in_new_tab": "Open request in new tab", - "set_environment_variable": "Set as variable" + "add_parameters": "Добавить в список параметров", + "open_request_in_new_tab": "Открыть запрос в новом окне", + "set_environment_variable": "Добавить значение в переменную" }, "cookies": { "modal": { @@ -225,27 +254,29 @@ "authorization": "Этот запрос не использует авторизацию", "body": "У этого запроса нет тела", "collection": "Коллекция пуста", - "collections": "Коллекции пустые", - "documentation": "Подключите GraphQL endpoint, чтобы увидеть документацию.", + "collections": "Коллекции пусты", + "documentation": "Подключите GraphQL endpoint, чтобы увидеть документацию", "endpoint": "Endpoint не может быть пустым", - "environments": "Окружения пусты", + "environments": "Переменных окружения нет", "folder": "Папка пуста", "headers": "У этого запроса нет заголовков", "history": "История пуста", "invites": "Вы еще никого не приглашали", "members": "В этой команде еще нет участников", - "parameters": "Этот запрос не имеет параметров", - "pending_invites": "Пока что нет ожидающих заявок на вступление в команду", + "parameters": "Этот запрос не содержит параметров", + "pending_invites": "Пока нет заявок, ожидающих вступления в команду", "profile": "Войдите, чтобы просмотреть свой профиль", - "protocols": "Протоколы пустые", + "protocols": "Протоколы пусты", + "request_variables": "Этот запрос не содержит никаких переменных", "schema": "Подключиться к конечной точке GraphQL", - "shared_requests": "Shared requests are empty", - "shared_requests_logout": "Login to view your shared requests or create a new one", + "secret_environments": "Секреты хранятся только на этом устройстве и не синхронизируются с сервером", + "shared_requests": "Вы еще не делились запросами с другими", + "shared_requests_logout": "Нужно войти, чтобы делиться запросами и управлять ими", "subscription": "Нет подписок", - "team_name": "Название команды пусто", + "team_name": "Название команды пустое", "teams": "Команды пустые", "tests": "Для этого запроса нет тестов", - "shortcodes": "Нет коротких ссылок" + "access_tokens": "Токенов еще нет" }, "environment": { "add_to_global": "Добавить в глобальное окружение", @@ -253,53 +284,59 @@ "create_new": "Создать новое окружение", "created": "Окружение создано", "deleted": "Окружение удалено", - "duplicated": "Environment duplicated", + "duplicated": "Окружение продублировано", "edit": "Редактировать окружение", - "empty_variables": "No variables", + "empty_variables": "Переменные еще не добавлены", "global": "Global", - "global_variables": "Global variables", + "global_variables": "Глобальные переменные", "import_or_create": "Импортировать или создать новое окружение", "invalid_name": "Укажите допустимое имя для окружения", "list": "Переменные окружения", "my_environments": "Мои окружения", - "name": "Name", + "name": "Имя", "nested_overflow": "максимальный уровень вложения переменных окружения - 10", "new": "Новая среда", "no_active_environment": "Нет активных окружений", "no_environment": "Нет окружения", "no_environment_description": "Не выбрано окружение, выберите что делать с переменными.", - "quick_peek": "Environment Quick Peek", + "quick_peek": "Быстрый просмотр переменных", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Секретные переменные", + "secret_value": "Секретное значение", "select": "Выберите среду", - "set": "Set environment", - "set_as_environment": "Set as environment", + "set": "Выбрать окружение", + "set_as_environment": "Поместить значение в переменную", "team_environments": "Окружения команды", "title": "Окружения", "updated": "Окружение обновлено", - "value": "Value", - "variable": "Variable", - "variable_list": "Список переменных" + "value": "Значение", + "variable": "Переменная", + "variables": "Переменные", + "variable_list": "Список переменных", + "properties": "Environment Properties", + "details": "Details" }, "error": { - "authproviders_load_error": "Unable to load auth providers", - "browser_support_sse": "Похоже, в этом браузере нет поддержки событий, отправленных сервером.", + "authproviders_load_error": "Не получается загрузить список возможных вариантов входа. Проверьте сеть, возможно отключен VPN", + "browser_support_sse": "Похоже, в этом браузере нет поддержки событий, отправленных сервером", "check_console_details": "Подробности смотрите в журнале консоли.", - "check_how_to_add_origin": "Инструкция как добавить origin в настройки расширения", + "check_how_to_add_origin": "Инструкция как это сделать", "curl_invalid_format": "cURL неправильно отформатирован", "danger_zone": "Опасная зона", "delete_account": "Вы являетесь владельцем этой команды:", "delete_account_description": "Прежде чем удалить аккаунт вам необходимо либо назначить владельцом другого пользователя, либо удалить команды в которых вы являетесь владельцем.", + "empty_profile_name": "Имя пользователя не может быть пустым", "empty_req_name": "Пустое имя запроса", "f12_details": "(F12 для подробностей)", - "gql_prettify_invalid_query": "Не удалось определить недопустимый запрос, устранить синтаксические ошибки запроса и повторить попытку.", + "gql_prettify_invalid_query": "Не удалось отформатировать, т.к. в запросе есть синтаксические ошибки. Устраните их и повторите попытку.", "incomplete_config_urls": "Не заполнены URL конфигурации", - "incorrect_email": "Не корректный Email", - "invalid_link": "Не корректная ссылка", - "invalid_link_description": "Ссылка, по которой вы перешли, - недействительна, либо срок ее действия истек.", + "incorrect_email": "Некорректный Email", + "invalid_link": "Некорректная ссылка", + "invalid_link_description": "Ссылка, по которой вы перешли, недействительна, либо срок её действия истёк", "invalid_embed_link": "The embed does not exist or is invalid.", - "json_parsing_failed": "Не корректный JSON", - "json_prettify_invalid_body": "Не удалось определить недопустимое тело, устранить синтаксические ошибки json и повторить попытку.", + "json_parsing_failed": "Некорректный JSON", + "json_prettify_invalid_body": "Не удалось определить формат строки, устраните синтаксические ошибки и повторите попытку.", "network_error": "Похоже, возникла проблема с соединением. Попробуйте еще раз.", "network_fail": "Не удалось отправить запрос", "no_collections_to_export": "Нечего экспортировать. Для начала нужно создать коллекцию.", @@ -307,22 +344,26 @@ "no_environments_to_export": "Нечего экспортировать. Для начала нужно создать переменные окружения.", "no_results_found": "Совпадения не найдены", "page_not_found": "Эта страница не найдена", - "please_install_extension": "Нужно установить специальное расширение и добавить этот домен как новый origin в настройках расширения.", - "proxy_error": "Proxy error", + "please_install_extension": "Ничего страшного. Просто нужно установить специальное расширение в браузере.", + "proxy_error": "Ошибка в прокси", + "same_profile_name": "Задано имя пользователя такое же как и было", "script_fail": "Не удалось выполнить сценарий предварительного запроса", "something_went_wrong": "Что-то пошло не так", - "test_script_fail": "Не удалось выполнить тестирование запроса" + "test_script_fail": "Не удалось выполнить тестирование запроса", + "reading_files": "Произошла ошибка при чтении файла или нескольких файлов", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Что-то пошло не так в процессе генерации токена доступа", + "delete_access_token": "Что-то пошло не так в процессе удаления токена доступа" }, "export": { "as_json": "Экспорт как JSON", "create_secret_gist": "Создать секретный Gist", - "create_secret_gist_tooltip_text": "Export as secret Gist", - "failed": "Something went wrong while exporting", - "secret_gist_success": "Successfully exported as secret Gist", + "create_secret_gist_tooltip_text": "Экспортировать как секретный Gist", + "failed": "Произошла ошибка во время экспорта", + "secret_gist_success": "Успешно экспортировано как секретный Gist", "require_github": "Войдите через GitHub, чтобы создать секретную суть", "title": "Экспорт", - "success": "Successfully exported", - "gist_created": "Gist создан" + "success": "Успешно экспортировано" }, "filter": { "all": "Все", @@ -344,10 +385,11 @@ "mutations": "Мутации", "schema": "Схема", "subscriptions": "Подписки", - "switch_connection": "Изменить соединение" + "switch_connection": "Изменить соединение", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { - "title": "GraphQL Collections" + "title": "Коллекции GraphQL" }, "group": { "time": "Время", @@ -359,16 +401,16 @@ "save_workspace": "Сохранить мою рабочую область" }, "helpers": { - "authorization": "Заголовок авторизации будет автоматически сгенерирован при отправке запроса.", - "collection_properties_authorization": " This authorization will be set for every request in this collection.", - "collection_properties_header": "This header will be set for every request in this collection.", + "authorization": "Заголовок авторизации будет автоматически сгенерирован при отправке запроса", + "collection_properties_authorization": "Этот заголовок авторизации будет подставляться при каждом запросе в этой коллекции", + "collection_properties_header": "Этот заголовок будет подставляться при каждом запросе в этой коллекции", "generate_documentation_first": "Сначала создайте документацию", - "network_fail": "Невозможно достичь конечной точки API. Проверьте подключение к сети и попробуйте еще раз.", - "offline": "Кажется, вы не в сети. Данные в этой рабочей области могут быть устаревшими.", - "offline_short": "Кажется, вы не в сети.", - "post_request_tests": "Сценарии тестирования написаны на JavaScript и запускаются после получения ответа.", - "pre_request_script": "Скрипты предварительного запроса написаны на JavaScript и запускаются перед отправкой запроса.", - "script_fail": "Похоже, в скрипте предварительного запроса есть сбой. Проверьте ошибку ниже и исправьте скрипт соответствующим образом.", + "network_fail": "Невозможно достичь конечной точки API. Проверьте подключение к сети и попробуйте еще раз", + "offline": "Кажется, вы не в сети. Данные в этой рабочей области могут быть устаревшими", + "offline_short": "Кажется, вы не в сети", + "post_request_tests": "Сценарии тестирования написаны на JavaScript и запускаются после получения ответа", + "pre_request_script": "Скрипты предварительного запроса написаны на JavaScript и запускаются перед отправкой запроса", + "script_fail": "Похоже, в скрипте предварительного запроса есть сбой. Проверьте ошибку ниже и исправьте скрипт соответствующим образом", "test_script_fail": "Похоже, что скрипт тестирования содержит ошибку. Пожалуйста исправьте её и попробуйте снова", "tests": "Напишите тестовый сценарий для автоматизации отладки." }, @@ -381,13 +423,13 @@ "import": { "collections": "Импортировать коллекции", "curl": "Импортировать из cURL", - "environments_from_gist": "Import From Gist", - "environments_from_gist_description": "Import Hoppscotch Environments From Gist", + "environments_from_gist": "Импортировать из Gist", + "environments_from_gist_description": "Импортировать переменные окружения Hoppscotch из Gist", "failed": "Ошибка импорта", - "from_file": "Import from File", + "from_file": "Импортировать из одного или нескольких файлов", "from_gist": "Импорт из Gist", "from_gist_description": "Импортировать через Gist URL", - "from_insomnia": "Импортировать с Insomnia", + "from_insomnia": "Импортировать из Insomnia", "from_insomnia_description": "Импортировать из коллекции Insomnia", "from_json": "Импортировать из Hoppscotch", "from_json_description": "Импортировать из файла коллекции Hoppscotch", @@ -399,9 +441,9 @@ "from_postman_description": "Импортировать из коллекции Postman", "from_url": "Импортировать из URL", "gist_url": "Введите URL-адрес Gist", - "gql_collections_from_gist_description": "Import GraphQL Collections From Gist", + "gql_collections_from_gist_description": "Импортировать GraphQL коллекцию из Gist", "hoppscotch_environment": "Hoppscotch Environment", - "hoppscotch_environment_description": "Import Hoppscotch Environment JSON file", + "hoppscotch_environment_description": "Импортировать окружение Hoppscotch из JSON файла", "import_from_url_invalid_fetch": "Не удалить получить данные по этому URL", "import_from_url_invalid_file_format": "Ошибка при импорте коллекций", "import_from_url_invalid_type": "Неподдерживаемый тип. Поддерживаемые типы: 'hoppscotch', 'openapi', 'postman', 'insomnia'", @@ -409,31 +451,36 @@ "insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file", "json_description": "Импортировать из коллекции Hoppscotch", "postman_environment": "Postman Environment", - "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Импортировать" + "postman_environment_description": "Импортировать переменные окружения Postman из JSON файла", + "title": "Импортировать", + "file_size_limit_exceeded_warning_multiple_files": "Выбранные файлы превышают рекомендованный лимит в 10MB. Были импортированы только первые {files}", + "file_size_limit_exceeded_warning_single_file": "Размер выбранного в данный момент файла превышает рекомендуемый лимит в 10 МБ. Пожалуйста, выберите другой файл", + "success": "Успешно импортировано" }, "inspections": { - "description": "Inspect possible errors", + "description": "Показать возможные ошибки", "environment": { - "add_environment": "Add to Environment", - "not_found": "Environment variable “{environment}” not found." + "add_environment": "Добавить переменную", + "add_environment_value": "Заполнить значение", + "empty_value": "Значение переменной окружения '{variable}' пустое", + "not_found": "Переменная окружения “{environment}” не задана" }, "header": { - "cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead." + "cookie": "Из-за ограничений безопасности в веб версии нельзя задать Cookie параметры. Пожалуйста, используйте приложение Hoppscotch Desktop или используйте заголовок Authorization для веб-версии" }, "response": { - "401_error": "Please check your authentication credentials.", - "404_error": "Please check your request URL and method type.", - "cors_error": "Please check your Cross-Origin Resource Sharing configuration.", - "default_error": "Please check your request.", - "network_error": "Please check your network connection." + "401_error": "Проверьте данные для аутентификации", + "404_error": "Проверьте параметры запроса и метод HTTP", + "cors_error": "Проверьте настройки CORS (Cross-Origin Resource Sharing)", + "default_error": "Проверьте параметры запроса", + "network_error": "Проверьте сетевое подключение" }, - "title": "Inspector", + "title": "Помощник", "url": { - "extension_not_installed": "Extension not installed.", - "extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.", - "extention_enable_action": "Enable Browser Extension", - "extention_not_enabled": "Extension not enabled." + "extension_not_installed": "Расширение не установлено", + "extension_unknown_origin": "Убедитесь, что текущий домен добавлен в список доверенных ресурсов в расширении браузера", + "extention_enable_action": "Подключить расширение", + "extention_not_enabled": "Расширение в браузере не подключено" } }, "layout": { @@ -444,13 +491,13 @@ "row": "Горизонтальная развертка" }, "modal": { - "close_unsaved_tab": "У вас есть не сохранённые изменения", + "close_unsaved_tab": "У вас есть несохранённые изменения", "collections": "Коллекции", - "confirm": "Подтверждать", - "customize_request": "Customize Request", + "confirm": "Подтвердите действие", + "customize_request": "Настроить вид запроса", "edit_request": "Изменить запрос", "import_export": "Импорт Экспорт", - "share_request": "Share Request" + "share_request": "Поделиться запросом" }, "mqtt": { "already_subscribed": "Вы уже подписаны на этот топик", @@ -461,7 +508,7 @@ "color": "Выбрать цвет", "communication": "Коммуникация", "connection_config": "Конфигурация соединения", - "connection_not_authorized": "Это соединение MQTT не использует какую-либо авторизацию.", + "connection_not_authorized": "Это соединение MQTT не использует какую-либо авторизацию", "invalid_topic": "Пожалуйста выберите topic для подписки", "keep_alive": "Поддерживать соединение", "log": "Лог", @@ -471,7 +518,7 @@ "lw_topic": "Last-Will Topic", "message": "Сообщение", "new": "Новая подписка", - "not_connected": "Пожалуйста, сначала запустите MQTT соединение.", + "not_connected": "Пожалуйста, сначала запустите MQTT соединение", "publish": "Публиковать", "qos": "QoS", "ssl": "SSL", @@ -486,30 +533,30 @@ "doc": "Документы", "graphql": "GraphQL", "profile": "Профиль", - "realtime": "В реальном времени", + "realtime": "Realtime", "rest": "REST", "settings": "Настройки" }, "preRequest": { "javascript_code": "Код JavaScript", "learn": "Читать документацию", - "script": "Предворительный скрипт запроса", + "script": "Предварительный скрипт запроса", "snippets": "Готовый код" }, "profile": { "app_settings": "Настройки приложения", "default_hopp_displayname": "Безымянный", "editor": "Редактор", - "editor_description": "Редакторы могут добавлять, редактировать, а так же удалять запросы.", - "email_verification_mail": "На вашу электронную почту отправлено письмо для подтверждения. Перейдите по ссылке из письма, чтобы подтвердить свой электронный адрес.", - "no_permission": "У Вас недостаточно прав, чтобы выполнить это действие.", + "editor_description": "Редакторы могут добавлять, редактировать, а так же удалять запросы", + "email_verification_mail": "На вашу электронную почту отправлено письмо для подтверждения. Перейдите по ссылке из письма, чтобы подтвердить свой электронный адрес", + "no_permission": "У Вас недостаточно прав, чтобы выполнить это действие", "owner": "Владелец", - "owner_description": "Владелец может добавлять, редактировать, и удалять запросы, коллекии, а так же участников.", + "owner_description": "Владелец может добавлять, редактировать и удалять запросы, коллекции, а так же участников", "roles": "Роли", - "roles_description": "Роли позволяют настраивать доступ конкретным людям к публичным коллекциям.", + "roles_description": "Роли позволяют настраивать доступ конкретным людям к публичным коллекциям", "updated": "Профиль обновлен", - "viewer": "Зритель", - "viewer_description": "Зрительно могут только просматривать и использовать запросы." + "viewer": "Читатель", + "viewer_description": "Могут только просматривать и использовать запросы" }, "remove": { "star": "Удалить звезду" @@ -531,11 +578,11 @@ "enter_curl": "Введите сюда команду cURL", "generate_code": "Сгенерировать код", "generated_code": "Сгенерированный код", - "go_to_authorization_tab": "Go to Authorization", - "go_to_body_tab": "Go to Body tab", + "go_to_authorization_tab": "Перейти на вкладку авторизации", + "go_to_body_tab": "Перейти на вкладку тела запроса", "header_list": "Список заголовков", "invalid_name": "Укажите имя для запроса", - "method": "Методика", + "method": "Метод", "moved": "Запрос перемещён", "name": "Имя запроса", "new": "Новый запрос", @@ -549,22 +596,23 @@ "payload": "Полезная нагрузка", "query": "Запрос", "raw_body": "Необработанное тело запроса", - "rename": "Переименость запрос", + "rename": "Переименовать запрос", "renamed": "Запрос переименован", + "request_variables": "Переменные запроса", "run": "Запустить", "save": "Сохранить", "save_as": "Сохранить как", "saved": "Запрос сохранен", - "share": "Делиться", + "share": "Поделиться", "share_description": "Поделиться Hoppscotch с друзьями", - "share_request": "Share Request", - "stop": "Stop", + "share_request": "Поделиться запросом", + "stop": "Стоп", "title": "Запрос", "type": "Тип запроса", "url": "URL", + "url_placeholder": "Введите URL или вставьте команду из cURL", "variables": "Переменные", - "view_my_links": "Посмотреть мои ссылки", - "copy_link": "Копировать ссылку" + "view_my_links": "Посмотреть мои ссылки" }, "response": { "audio": "Аудио", @@ -576,7 +624,7 @@ "json": "JSON", "pdf": "PDF", "preview_html": "Предварительный просмотр HTML", - "raw": "Необработанное", + "raw": "RAW", "size": "Размер", "status": "Статус", "time": "Время", @@ -587,31 +635,31 @@ }, "settings": { "accent_color": "Основной цвет", - "account": "Счет", + "account": "Аккаунт", "account_deleted": "Ваш аккаунт был удалён", "account_description": "Настройте параметры своей учетной записи.", "account_email_description": "Ваш основной адрес электронной почты.", "account_name_description": "Это ваше отображаемое имя.", "additional": "Additional Settings", "background": "Задний фон", - "black_mode": "Темная тема", + "black_mode": "Чёрная", "choose_language": "Выберите язык", - "dark_mode": "Темный", + "dark_mode": "Тёмная", "delete_account": "Удалить аккаунт", "delete_account_description": "Удаление аккаунта нельзя отменить", - "expand_navigation": "Раскрыть панель навигации", + "expand_navigation": "Раскрыть боковую панель", "experiments": "Эксперименты", - "experiments_notice": "Это набор экспериментов, над которыми мы работаем, которые могут оказаться полезными, интересными, и тем, и другим, или ни тем, ни другим. Они не окончательные и могут быть нестабильными, поэтому, если произойдет что-то слишком странное, не паникуйте. Просто выключи эту чертову штуку. Шутки в сторону,", + "experiments_notice": "Это набор экспериментов, над которыми мы работаем, которые могут оказаться полезными, интересными тебе и мне, а может и никому. Они не завершены и могут быть нестабильными, поэтому, если произойдет что-то слишком странное, не паникуйте. Просто выключи эту чертову штуку. Шутки в сторону,", "extension_ver_not_reported": "Не сообщается", "extension_version": "Версия расширения", "extensions": "Расширения", "extensions_use_toggle": "Используйте расширение браузера для отправки запросов (если есть)", "follow": "Follow Us", "interceptor": "Перехватчик", - "interceptor_description": "Промежуточное ПО между приложением и API.", + "interceptor_description": "Промежуточное ПО между приложением и API", "language": "Язык", - "light_mode": "Свет", - "official_proxy_hosting": "Официальный прокси-сервер размещен на Hoppscotch.", + "light_mode": "Светлая", + "official_proxy_hosting": "Официальный прокси-сервер размещен на Hoppscotch", "profile": "Профиль", "profile_description": "Обновить настройки профиля", "profile_email": "Адрес электронной почты", @@ -626,12 +674,12 @@ "sidebar_on_left": "Панель слева", "sync": "Синхронизировать", "sync_collections": "Коллекции", - "sync_description": "Эти настройки синхронизируются с облаком.", + "sync_description": "Эти настройки синхронизируются с облаком", "sync_environments": "Среды", "sync_history": "История", "system_mode": "Система", "telemetry": "Телеметрия", - "telemetry_helps_us": "Телеметрия помогает нам персонализировать наши операции и предоставлять вам лучший опыт.", + "telemetry_helps_us": "Телеметрия помогает нам персонализировать наши операции и предоставлять вам лучший опыт", "theme": "Тема", "theme_description": "Настройте тему своего приложения.", "use_experimental_url_bar": "Использовать экспериментальную строку URL с выделением среды", @@ -640,29 +688,29 @@ "verify_email": "Подтвердить Email" }, "shared_requests": { - "button": "Button", - "button_info": "Create a 'Run in Hoppscotch' button for your website, blog or a README.", - "copy_html": "Copy HTML", - "copy_link": "Copy Link", - "copy_markdown": "Copy Markdown", - "creating_widget": "Creating widget", - "customize": "Customize", - "deleted": "Shared request deleted", - "description": "Select a widget, you can change and customize this later", - "embed": "Embed", - "embed_info": "Add a mini 'Hoppscotch API Playground' to your website, blog or documentation.", - "link": "Link", - "link_info": "Create a shareable link to share with anyone on the internet with view access.", - "modified": "Shared request modified", - "not_found": "Shared request not found", - "open_new_tab": "Open in new tab", - "preview": "Preview", - "run_in_hoppscotch": "Run in Hoppscotch", + "button": "Кнопка", + "button_info": "Добавить кнопку 'Run in Hoppscotch' на свой сайт, блог или README", + "copy_html": "Копировать HTML код", + "copy_link": "Копировать ссылку", + "copy_markdown": "Копировать Markdown", + "creating_widget": "Создание виджета", + "customize": "Настроить", + "deleted": "Запрос удален", + "description": "Выберите, каким образом вы хотите поделиться запросом. Вы сможете добавить дополнительные настройки кастомизации позже", + "embed": "Встраиваемое окно", + "embed_info": "Добавьте небольшую площадку 'Hoppscotch API Playground' на свой веб-сайт, блог или документацию", + "link": "Ссылка", + "link_info": "Создайте общедоступную ссылку, которой можно поделиться с любым пользователем", + "modified": "Запрос изменен", + "not_found": "Такой ссылки не нашлось", + "open_new_tab": "Открыть в новом окне", + "preview": "Как это будет выглядеть:", + "run_in_hoppscotch": "Запустить в Hoppscotch", "theme": { - "dark": "Dark", - "light": "Light", - "system": "System", - "title": "Theme" + "dark": "Темная", + "light": "Светлая", + "system": "Системная", + "title": "Тема" } }, "shortcut": { @@ -670,7 +718,7 @@ "close_current_menu": "Закрыть текущее меню", "command_menu": "Меню поиска и команд", "help_menu": "Меню помощи", - "show_all": "Горячие клавиши", + "show_all": "Список горячих клавиш", "title": "Общий" }, "miscellaneous": { @@ -697,20 +745,19 @@ "get_method": "Выберите метод GET", "head_method": "Выберите метод HEAD", "import_curl": "Импортировать из cURL", - "method": "Методика", + "method": "Метод", "next_method": "Выберите следующий метод", "post_method": "Выберите метод POST", "previous_method": "Выбрать предыдущий метод", "put_method": "Выберите метод PUT", "rename": "Переименовать запрос", "reset_request": "Сбросить запрос", - "save_request": "Сохарнить запрос", + "save_request": "Сохранить запрос", "save_to_collections": "Сохранить в коллекции", - "send_request": "Послать запрос", - "share_request": "Share Request", - "show_code": "Generate code snippet", - "title": "Запрос", - "copy_request_link": "Копировать ссылку на запрос" + "send_request": "Отправить запрос", + "share_request": "Поделиться запросом", + "show_code": "Сгенерировать фрагмент кода из запроса", + "title": "Запрос" }, "response": { "copy": "Копировать запрос в буфер обмена", @@ -718,11 +765,11 @@ "title": "Запрос" }, "theme": { - "black": "Черный режим", - "dark": "Тёмный режим", - "light": "Светлый режим", - "system": "Определяется системой", - "title": "Тема" + "black": "Переключить на черный режим", + "dark": "Переключить на тёмный режим", + "light": "Переключить на светлый режим", + "system": "Переключить на тему, исходя из настроек системы", + "title": "Внешний вид" } }, "show": { @@ -740,82 +787,89 @@ "url": "URL" }, "spotlight": { - "change_language": "Change Language", + "change_language": "Изменить язык", "environments": { - "delete": "Delete current environment", - "duplicate": "Duplicate current environment", - "duplicate_global": "Duplicate global environment", - "edit": "Edit current environment", - "edit_global": "Edit global environment", - "new": "Create new environment", - "new_variable": "Create a new environment variable", - "title": "Environments" + "delete": "Удалить текущее окружение", + "duplicate": "Дублировать текущее окружение", + "duplicate_global": "Дублировать глобальное окружение", + "edit": "Редактировать текущее окружение", + "edit_global": "Редактировать глобальное окружение", + "new": "Создать новое окружение", + "new_variable": "Создать новую переменную окружения", + "title": "Окружение" }, "general": { - "chat": "Chat with support", - "help_menu": "Help and support", - "open_docs": "Read Documentation", - "open_github": "Open GitHub repository", - "open_keybindings": "Keyboard shortcuts", - "social": "Social", - "title": "General" + "chat": "Чат с поддержкой", + "help_menu": "Помощь", + "open_docs": "Почитать документацию", + "open_github": "Открыть GitHub репозиторий", + "open_keybindings": "Горячие клавиши", + "social": "Соц. сети", + "title": "Общее" }, "graphql": { - "connect": "Connect to server", - "disconnect": "Disconnect from server" + "connect": "Подключиться к серверу", + "disconnect": "Отключиться от сервера" }, "miscellaneous": { - "invite": "Invite your friends to Hoppscotch", - "title": "Miscellaneous" + "invite": "Пригласить друзей в Hoppscotch", + "title": "Другое" }, "request": { - "save_as_new": "Save as new request", - "select_method": "Select method", - "switch_to": "Switch to", - "tab_authorization": "Authorization tab", - "tab_body": "Body tab", - "tab_headers": "Headers tab", - "tab_parameters": "Parameters tab", - "tab_pre_request_script": "Pre-request script tab", - "tab_query": "Query tab", - "tab_tests": "Tests tab", - "tab_variables": "Variables tab" + "save_as_new": "Сохранить как новый запрос", + "select_method": "Выбрать метод", + "switch_to": "Переключиться", + "tab_authorization": "На вкладку авторизации", + "tab_body": "На вкладку тела запроса", + "tab_headers": "На вкладку заголовков", + "tab_parameters": "На вкладку параметров", + "tab_pre_request_script": "На вкладку пред-скрипта запроса", + "tab_query": "На вкладку запроса", + "tab_tests": "На вкладку тестов", + "tab_variables": "На вкладку переменных запроса" }, "response": { - "copy": "Copy response", - "download": "Download response as file", - "title": "Response" + "copy": "Копировать содержимое ответа", + "download": "Сказать содержимое ответа как файл", + "title": "Ответ запроса" }, "section": { - "interceptor": "Interceptor", - "interface": "Interface", - "theme": "Theme", - "user": "User" + "interceptor": "Перехватчик", + "interface": "Интерфейс", + "theme": "Внешний вид", + "user": "Пользователь" }, "settings": { - "change_interceptor": "Change Interceptor", - "change_language": "Change Language", + "change_interceptor": "Изменить перехватчик", + "change_language": "Изменить язык", "theme": { - "black": "Black", - "dark": "Dark", - "light": "Light", - "system": "System preference" + "black": "Черная", + "dark": "Темная", + "light": "Светлая", + "system": "Как задано в системе" } }, "tab": { - "close_current": "Close current tab", - "close_others": "Close all other tabs", - "duplicate": "Duplicate current tab", - "new_tab": "Open a new tab", - "title": "Tabs" + "close_current": "Закрыть текущую вкладку", + "close_others": "Закрыть все вкладки", + "duplicate": "Продублировать текущую вкладку", + "new_tab": "Открыть в новой вкладке", + "title": "Вкладки" }, "workspace": { - "delete": "Delete current team", - "edit": "Edit current team", - "invite": "Invite people to team", - "new": "Create new team", - "switch_to_personal": "Switch to your personal workspace", - "title": "Teams" + "delete": "Удалить текущую команду", + "edit": "Редактировать текущую команду", + "invite": "Пригласить людей в команду", + "new": "Создать новую команду", + "switch_to_personal": "Переключить на персональное пространство", + "title": "Команды" + }, + "phrases": { + "try": "Попробуй", + "import_collections": "Импортировать коллекцию", + "create_environment": "Создать окружение", + "create_workspace": "Создать пространство", + "share_request": "Поделиться запросом" } }, "sse": { @@ -825,9 +879,9 @@ }, "state": { "bulk_mode": "Множественное редактирование", - "bulk_mode_placeholder": "Каждый параметр должен начинаться с новой строки\nКлючи и значения разедляются двоеточием\nИспользуйте # для комментария", + "bulk_mode_placeholder": "Каждый параметр должен начинаться с новой строки\nКлючи и значения разделяются двоеточием\nИспользуйте # для комментария", "cleared": "Очищено", - "connected": "Связаны", + "connected": "Подключено", "connected_to": "Подключено к {name}", "connecting_to": "Подключение к {name} ...", "connection_error": "Ошибка подключения", @@ -838,26 +892,26 @@ "deleted": "Удалено", "deprecated": "УСТАРЕЛО", "disabled": "Отключено", - "disconnected": "Отключен", + "disconnected": "Нет подключения", "disconnected_from": "Отключено от {name}", "docs_generated": "Документация создана", "download_failed": "Download failed", "download_started": "Скачивание началось", "enabled": "Включено", - "file_imported": "Файл импортирован", + "file_imported": "Файл успешно импортирован", "finished_in": "Завершено через {duration} мс", - "hide": "Hide", + "hide": "Скрыть", "history_deleted": "История удалена", - "linewrap": "Обернуть линии", + "linewrap": "Переносить строки", "loading": "Загрузка...", "message_received": "Сообщение: {message} получено по топику: {topic}", "mqtt_subscription_failed": "Что-то пошло не так, при попытке подписаться на топик: {topic}", - "none": "Никто", + "none": "Не задан", "nothing_found": "Ничего не найдено для", "published_error": "Что-то пошло не так при попытке опубликовать сообщение в топик {topic}: {message}", "published_message": "Опубликовано сообщение: {message} в топик: {topic}", "reconnection_error": "Не удалось переподключиться", - "show": "Show", + "show": "Показать", "subscribed_failed": "Не удалось подписаться на топик: {topic}", "subscribed_success": "Успешно подписался на топик: {topic}", "unsubscribed_failed": "Не удалось отписаться от топика: {topic}", @@ -866,13 +920,12 @@ }, "support": { "changelog": "Узнать больше о последних выпусках", - "chat": "Вопросов? Поболтай с нами!", + "chat": "Есть вопрос? Давай поболтаем!", "community": "Задавайте вопросы и помогайте другим", "documentation": "Узнать больше о Hoppscotch", "forum": "Задавайте вопросы и получайте ответы", "github": "Подпишитесь на нас на Github", - "shortcuts": "Просматривайте приложение быстрее", - "team": "Свяжитесь с командой", + "shortcuts": "Работайте с приложением ещё быстрее", "title": "Служба поддержки", "twitter": "Следуйте за нами на Twitter" }, @@ -883,7 +936,7 @@ "close_others": "Закрыть остальные вкладки", "collections": "Коллекции", "documentation": "Документация", - "duplicate": "Duplicate Tab", + "duplicate": "Дублировать вкладку", "environments": "Окружения", "headers": "Заголовки", "history": "История", @@ -893,7 +946,10 @@ "queries": "Запросы", "query": "Запрос", "schema": "Схема", - "shared_requests": "Shared Requests", + "shared_requests": "Запросы в общем доступе", + "codegen": "Сгенерировать код", + "code_snippet": "Фрагмент кода", + "share_tab_request": "Поделиться запросом", "socketio": "Socket.IO", "sse": "SSE", "tests": "Тесты", @@ -920,11 +976,10 @@ "invite": "Пригласить", "invite_more": "Пригласить больше", "invite_tooltip": "Пригласить людей в Ваше рабочее пространство", - "invited_to_team": "{owner} приглашает Вас присоединиться к команде {team}", + "invited_to_team": "{owner} приглашает Вас присоединиться к пространству {workspace}", "join": "Приглашение принято", - "join_beta": "Присоединяйтесь к бета-программе, чтобы получить доступ к командам.", - "join_team": "Присоединиться к {team}", - "joined_team": "Вы присоединились к команде {team}", + "join_team": "Присоединиться к {workspace}", + "joined_team": "Вы присоединились к команде {workspace}", "joined_team_description": "Теперь Вы участник этой команды", "left": "Вы покинули команду", "login_to_continue": "Войдите для продолжения", @@ -936,7 +991,7 @@ "member_role_updated": "Роли пользователей обновлены", "members": "Участники", "more_members": "+{count}", - "name_length_insufficient": "Название команды должно быть не менее 6 символов.", + "name_length_insufficient": "Название команды должно составлять не менее 6 символов", "name_updated": "Название команды обновлено", "new": "Новая команда", "new_created": "Создана новая команда", @@ -947,7 +1002,7 @@ "not_found": "Команда не найдена, свяжитесь с владельцем команды", "not_valid_viewer": "У Вас нет прав просматривать это. Свяжитесь с руководителем команды.", "parent_coll_move": "Не удалось переместить коллекцию в дочернюю", - "pending_invites": "Ожидающие приглашения", + "pending_invites": "Ждут добавления", "permissions": "Разрешения", "same_target_destination": "Таже цель и конечная точка", "saved": "Команда сохранена", @@ -955,7 +1010,11 @@ "success_invites": "Принятые приглашения", "title": "Команды", "we_sent_invite_link": "Мы отправили все приглашения!", - "we_sent_invite_link_description": "Попросите тех, кого Вы пригласили, проверить их почтовые ящики. Им нужно перейди по ссылке, чтобы подтвердить вступление в эту команду." + "invite_sent_smtp_disabled": "Ссылка-приглашение сгенерирована", + "we_sent_invite_link_description": "Попросите тех, кого Вы пригласили, проверить их почтовые ящики. Им нужно перейди по ссылке, чтобы подтвердить вступление в эту команду.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Скопировать ссылку-приглашение", + "search_title": "Team Requests" }, "team_environment": { "deleted": "Окружение удалено", @@ -967,8 +1026,8 @@ "javascript_code": "Код JavaScript", "learn": "Читать документацию", "passed": "Тест пройден", - "report": "Отчет об испытаниях", - "results": "Результаты теста", + "report": "Отчет", + "results": "Результаты", "script": "Скрипт", "snippets": "Фрагменты" }, @@ -982,16 +1041,48 @@ "workspace": { "change": "Изменить пространство", "personal": "Моё пространство", + "other_workspaces": "Пространства", "team": "Пространство команды", "title": "Рабочие пространства" }, - "shortcodes": { - "actions": "Действия", - "created_on": "Создано", - "deleted": "Удалёна", - "method": "Метод", - "not_found": "Короткая ссылка не найдена", - "short_code": "Короткая ссылка", - "url": "URL" + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Токены", + "section_title": "Персональные токены доступа", + "section_description": "С помощью персональных токенов доступа можно подключиться к вашей учётной записи Hoppscotch через CLI", + "last_used_on": "Последнее использование", + "expires_on": "Истекает", + "no_expiration": "Бессрочный", + "expired": "Истёк", + "copy_token_warning": "Обязательно скопируйте и сохраните свой персональный токен доступа. Вы больше с ним не увидитесь!", + "token_purpose": "Для чего будет использоваться токен?", + "expiration_label": "Время жизни", + "scope_label": "Ограничения", + "workspace_read_only_access": "Доступ к данным рабочей области только для чтения", + "personal_workspace_access_limitation": "Личные токены доступа не позволяют получить доступ к вашему личному рабочему пространству", + "generate_token": "Сгенерировать токен", + "invalid_label": "Придумайте название для токена", + "no_expiration_verbose": "Токен будет жить вечно!", + "token_expires_on": "Токен истечёт", + "generate_new_token": "Сгенерировать токен", + "generate_modal_title": "Новый персональный токен доступа", + "deletion_success": "Токен доступа {label} удалён" + }, + "collection_runner": { + "collection_id": "ID коллекции", + "environment_id": "ID окружения", + "cli_collection_id_description": "Этот ID будет использован для запуска коллекций через CLI в Hoppscoth", + "cli_environment_id_description": "Этот ID окружения будет использован для запуска коллекций через CLI в Hoppscoth", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (в разработке)", + "cli_command_generation_description_cloud": "Скопируйте команду и запустите её в CLI. Заполните параметр своим токеном", + "cli_command_generation_description_sh": "Скопируйте команду и запустите её в CLI. Заполните параметр своим токеном и проверьте, что сгенерирован верный URL сервера", + "cli_command_generation_description_sh_with_server_url_placeholder": "Скопируйте команду и запустите её в CLI. Заполните параметр своим токеном и определите URL сервера", + "run_collection": "Запустить коллекцию" } } diff --git a/packages/hoppscotch-common/locales/sr.json b/packages/hoppscotch-common/locales/sr.json index bc613bab41..a38a606a4a 100644 --- a/packages/hoppscotch-common/locales/sr.json +++ b/packages/hoppscotch-common/locales/sr.json @@ -24,8 +24,10 @@ "go_back": "Вратити се", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Ознака", "learn_more": "Сазнајте више", + "download_here": "Download here", "less": "Less", "more": "Више", "new": "Нова", @@ -43,6 +45,7 @@ "search": "Претрага", "send": "Пошаљи", "share": "Share", + "show_secret": "Show secret", "start": "Почетак", "starting": "Starting", "stop": "Зауставити", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Унесите команду или претражите…", "we_use_cookies": "Користимо колачиће", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Шта је ново?", + "see_whats_new": "See what’s new", "wiki": "Вики" }, "auth": { "account_exists": "Налог постоји са различитим акредитивима - Пријавите се да бисте повезали оба налога", "all_sign_in_options": "Све опције пријављивања", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Наставите са е -поштом", "continue_with_github": "Наставите са ГитХуб -ом", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Наставите са Гоогле -ом", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "Наставите са ОИДЦ-ом", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Лозинка", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Токен", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Измени збирку", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Наведите важећи назив збирке", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Изаберите колекцију", "select_location": "Изаберите локацију", + "details": "Details", "select_team": "Изаберите тим", "team_collections": "Збирке тима" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Јесте ли сигурни да желите да искључите Телеметрију?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Јесте ли сигурни да желите да синхронизујете овај радни простор?" + "sync": "Јесте ли сигурни да желите да синхронизујете овај радни простор?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "There are no pending invites for this team", "profile": "Login to view your profile", "protocols": "Протоколи су празни", + "request_variables": "This request does not have any request variables", "schema": "Повежите се са ГрапхКЛ крајњом тачком", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Назив тима празан", "teams": "Тимови су празни", "tests": "Нема тестова за овај захтев", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Изаберите окружење", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment updation", "value": "Value", "variable": "Variable", - "variable_list": "Листа променљивих" + "variables": "Variables", + "variable_list": "Листа променљивих", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Празан назив захтева", "f12_details": "(Ф12 за детаље)", "gql_prettify_invalid_query": "Није могуће унапредити неважећи упит, решити грешке у синтакси упита и покушати поново", @@ -297,6 +337,7 @@ "incorrect_email": "Incorrect email", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "Није могуће унапредити неважеће тело, решити грешке у синтакси јсон -а и покушати поново", "network_error": "There seems to be a network error. Please try again.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Није могуће извршити скрипту пре захтева", "something_went_wrong": "Нешто није у реду", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Извези као ЈСОН", "create_secret_gist": "Направите тајну суштину", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Суштина створена", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Пријавите се са ГитХуб -ом да бисте креирали тајну суштину", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Суштина створена" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Мутације", "schema": "Схема", "subscriptions": "Претплате", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Увоз" + "title": "Увоз", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Сирово тело захтева", "rename": "Rename Request", "renamed": "Захтев је преименован", + "request_variables": "Request variables", "run": "Трцати", "save": "сачувати", "save_as": "Сачувај као", @@ -558,6 +614,7 @@ "title": "Захтев", "type": "Врста Захтева", "url": "УРЛ", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Променљиве", "view_my_links": "View my links", "copy_link": "Копирај везу" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Постављајте питања и добијте одговоре", "github": "Follow us on Github", "shortcuts": "Брже прегледајте апликацију", - "team": "Ступите у контакт са тимом", "title": "Подршка", - "twitter": "Пратите нас на Твиттер -у" + "twitter": "Пратите нас на Твиттер -у", + "team": "Ступите у контакт са тимом" }, "tab": { "authorization": "Овлашћење", @@ -890,6 +954,9 @@ "query": "Упит", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Соцкет.ИО", "sse": "ССЕ", "tests": "Тестови", @@ -918,7 +985,6 @@ "invite_tooltip": "Invite people to this workspace", "invited_to_team": "{owner} invited you to join {team}", "join": "Invitation accepted", - "join_beta": "Придружите се бета програму да бисте приступили тимовима.", "join_team": "Join {team}", "joined_team": "You have joined {team}", "joined_team_description": "You are now a member of this team", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Тимови", "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Придружите се бета програму да бисте приступили тимовима." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/sv.json b/packages/hoppscotch-common/locales/sv.json index 9bd2b946c4..87a7c09058 100644 --- a/packages/hoppscotch-common/locales/sv.json +++ b/packages/hoppscotch-common/locales/sv.json @@ -24,8 +24,10 @@ "go_back": "Gå tillbaka", "go_forward": "Go forward", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Märka", "learn_more": "Läs mer", + "download_here": "Download here", "less": "Less", "more": "Mer", "new": "Ny", @@ -43,6 +45,7 @@ "search": "Sök", "send": "Skicka", "share": "Share", + "show_secret": "Show secret", "start": "Start", "starting": "Starting", "stop": "Sluta", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Skriv ett kommando eller sök ...", "we_use_cookies": "Vi använder cookies", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Vad är nytt?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Kontot finns med olika uppgifter - Logga in för att länka båda kontona", "all_sign_in_options": "Alla inloggningsalternativ", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Fortsätt med e -post", "continue_with_github": "Fortsätt med GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Fortsätt med Google", "continue_with_microsoft": "Continue with Microsoft", "continue_with_oidc": "Fortsätt med OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Pass by", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Lösenord", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Tecken", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Redigera samling", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Ange ett giltigt namn för samlingen", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Välj en samling", "select_location": "Välj plats", + "details": "Details", "select_team": "Välj ett lag", "team_collections": "Lagsamlingar" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Är du säker på att du vill välja bort telemetri?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Är du säker på att du vill synkronisera den här arbetsytan?" + "sync": "Är du säker på att du vill synkronisera den här arbetsytan?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "There are no pending invites for this team", "profile": "Login to view your profile", "protocols": "Protokoll är tomma", + "request_variables": "This request does not have any request variables", "schema": "Anslut till en GraphQL -slutpunkt", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Lagets namn är tomt", "teams": "Lag är tomma", "tests": "Det finns inga tester för denna begäran", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes are empty" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Välj miljö", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Environment updation", "value": "Value", "variable": "Variable", - "variable_list": "Variabel lista" + "variables": "Variables", + "variable_list": "Variabel lista", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Tom förfrågningsnamn", "f12_details": "(F12 för detaljer)", "gql_prettify_invalid_query": "Det gick inte att pryda en ogiltig fråga, lösa frågesyntaxfel och försök igen", @@ -297,6 +337,7 @@ "incorrect_email": "Incorrect email", "invalid_link": "Invalid link", "invalid_link_description": "The link you clicked is invalid or expired.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Invalid JSON", "json_prettify_invalid_body": "Det gick inte att pryda en ogiltig kropp, lösa json -syntaxfel och försök igen", "network_error": "There seems to be a network error. Please try again.", @@ -308,17 +349,25 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Det gick inte att köra skriptet för förhandsbegäran", "something_went_wrong": "Något gick fel", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Exportera som JSON", "create_secret_gist": "Skapa hemlig Gist", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist skapad", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Logga in med GitHub för att skapa hemlig information", - "title": "Export" + "title": "Export", + "success": "Successfully exported", + "gist_created": "Gist skapad" }, "filter": { "all": "All", @@ -340,7 +389,8 @@ "mutations": "Mutationer", "schema": "Schema", "subscriptions": "Prenumerationer", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Importera" + "title": "Importera", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Raw Request Body", "rename": "Rename Request", "renamed": "Begäran bytt namn", + "request_variables": "Request variables", "run": "Springa", "save": "Spara", "save_as": "Spara som", @@ -558,6 +614,7 @@ "title": "Begäran", "type": "Typ av förfrågan", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Variabler", "view_my_links": "View my links", "copy_link": "Kopiera länk" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Ställ frågor och få svar", "github": "Follow us on Github", "shortcuts": "Bläddra snabbare i appen", - "team": "Ta kontakt med laget", "title": "Stöd", - "twitter": "Följ oss på Twitter" + "twitter": "Följ oss på Twitter", + "team": "Ta kontakt med laget" }, "tab": { "authorization": "Tillstånd", @@ -890,6 +954,9 @@ "query": "Fråga", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Tester", @@ -918,7 +985,6 @@ "invite_tooltip": "Invite people to this workspace", "invited_to_team": "{owner} invited you to join {team}", "join": "Invitation accepted", - "join_beta": "Gå med i betaprogrammet för att komma åt team.", "join_team": "Join {team}", "joined_team": "You have joined {team}", "joined_team_description": "You are now a member of this team", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Lag", "we_sent_invite_link": "We sent an invite link to all invitees!", - "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Gå med i betaprogrammet för att komma åt team." }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/tr.json b/packages/hoppscotch-common/locales/tr.json index 4ee0cb07e1..9284b0c9f9 100644 --- a/packages/hoppscotch-common/locales/tr.json +++ b/packages/hoppscotch-common/locales/tr.json @@ -17,15 +17,17 @@ "dismiss": "Boşver", "dont_save": "Don't save", "download_file": "Dosyayı Indir", - "drag_to_reorder": "Drag to reorder", + "drag_to_reorder": "Yeniden sıralamak için sürükleyin", "duplicate": "Klonla", "edit": "Düzenle", - "filter": "Filter", + "filter": "Filtre", "go_back": "Geri git", - "go_forward": "Go forward", + "go_forward": "İleri git", "group_by": "Group by", + "hide_secret": "Hide secret", "label": "Etiket", "learn_more": "Daha fazla bilgi edin", + "download_here": "Download here", "less": "Daha az", "more": "Daha fazla", "new": "Yeni", @@ -42,7 +44,8 @@ "scroll_to_top": "Scroll to top", "search": "Arama", "send": "Gönder", - "share": "Share", + "share": "Paylaş", + "show_secret": "Show secret", "start": "Başla", "starting": "Starting", "stop": "Dur", @@ -68,9 +71,9 @@ "developer_option": "Developer options", "developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.", "discord": "Discord", - "documentation": "Dökümanlar", + "documentation": "Dokümanlar", "github": "GitHub", - "help": "Yardım, geri bildirim ve dökümanlar", + "help": "Yardım, geri bildirim ve dokümanlar", "home": "Ana sayfa", "invite": "Davet et", "invite_description": "Hoppscotch'ta API'lerinizi oluşturmak ve yönetmek için basit ve sezgisel bir arayüz tasarladık. Hoppscotch, API'lerinizi oluşturmanıza, test etmenize, belgelemenize ve paylaşmanıza yardımcı olan bir araçtır.", @@ -95,23 +98,27 @@ "twitter": "Twitter", "type_a_command_search": "Bir komut yazın veya arayın…", "we_use_cookies": "Çerezleri kullanıyoruz", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Ne var ne yok?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Farklı kimlik bilgilerine sahip hesap var - Her iki hesabı birbirine bağlamak için giriş yapın", "all_sign_in_options": "Tüm oturum açma seçenekleri", + "continue_with_auth_provider": "{provider} ile devam et", "continue_with_email": "E-posta ile devam et", - "continue_with_github": "GitHub hesabı ile devam et", - "continue_with_google": "Google hesabı ile devam et", - "continue_with_microsoft": "Microsoft hesabı ile devam et", + "continue_with_github": "GitHub ile devam et", + "continue_with_github_enterprise": "GitHub Enterprise ile devam et", + "continue_with_google": "Google ile devam et", + "continue_with_microsoft": "Microsoft ile devam et", "continue_with_oidc": "OIDC hesabı ile devam et", "email": "E-posta", "logged_out": "Çıkış yapıldı", - "login": "Giriş yap", + "login": "Giriş", "login_success": "Başarıyla giriş yapıldı", "login_to_hoppscotch": "Hoppscotch'a giriş yapın", - "logout": "Çıkış yap", + "logout": "Çıkış", "re_enter_email": "E-mail adresinizi yeniden girin", "send_magic_link": "Sihirli bir bağlantı gönder", "sync": "Senkronizasyon", @@ -136,20 +143,42 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Şunla anahtar ekleyin", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Parola", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Jeton", - "type": "Yetki türü", + "type": "Yetki Türü", "username": "Kullanıcı adı" }, "collection": { "created": "Koleksiyon oluşturuldu", "different_parent": "Cannot reorder collection with different parent", "edit": "Koleksiyonu düzenle", - "import_or_create": "Import or create a collection", + "import_or_create": "Koleksiyon oluşturun veya içe aktarın", + "import_collection": "Koleksiyonu İçe Aktar", "invalid_name": "Lütfen koleksiyon için geçerli bir ad girin", "invalid_root_move": "Collection already in the root", "moved": "Başarıyla taşındı", @@ -159,15 +188,14 @@ "new": "Yeni koleksiyon", "order_changed": "Collection Order Updated", "properties": "Collection Properties", - "properties_updated": "Collection Properties Updated", + "properties_updated": "Koleksiyon Özellikleri Güncellendi", "renamed": "Koleksiyon yeniden adlandırıldı", "request_in_use": "Kullanımda istek", "save_as": "Farklı kaydet", "save_to_collection": "Save to Collection", "select": "Bir koleksiyon Seçin", "select_location": "Konum seçin", - "select_team": "Bir takım seçin", - "team_collections": "Takım koleksiyonları" + "details": "Details" }, "confirm": { "close_unsaved_tab": "Are you sure you want to close this tab?", @@ -184,7 +212,8 @@ "remove_telemetry": "Telemetriden çıkmak istediğinizden emin misiniz?", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "save_unsaved_tab": "Bu sekmede yapılan değişiklikleri kaydetmek istiyor musunuz?", - "sync": "Bu çalışma alanını senkronize etmek istediğinizden emin misiniz?" + "sync": "Bu çalışma alanını senkronize etmek istediğinizden emin misiniz?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -226,7 +255,7 @@ "body": "Bu isteğin bir gövdesi yok", "collection": "Koleksiyon boş", "collections": "Koleksiyonlar boş", - "documentation": "Dökümanları görmek için GraphQL uç noktasını bağlayın", + "documentation": "Dokümanları görmek için GraphQL uç noktasını bağlayın", "endpoint": "Uç nokta boş olamaz", "environments": "Ortamlar boş", "folder": "Klasör boş", @@ -238,14 +267,16 @@ "pending_invites": "Bu takım için bekleyen bir istek yok", "profile": "Bu profili görüntülemek için giriş yapın", "protocols": "Protokoller boş", + "request_variables": "This request does not have any request variables", "schema": "Bir GraphQL uç noktasına bağlanma", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Subscriptions are empty", "team_name": "Takım adı boş", "teams": "Takımlar boş", "tests": "Bu istek için test yok", - "shortcodes": "Shortcodes are empty" + "access_tokens": "Access tokens are empty" }, "environment": { "add_to_global": "Globale ekle", @@ -261,25 +292,30 @@ "import_or_create": "Import or create a environment", "invalid_name": "Lütfen ortam için geçerli bir ad girin", "list": "Environment variables", - "my_environments": "My Environments", + "my_environments": "Ortamlarım", "name": "Name", "nested_overflow": "İç içe ortam değişkenleri 10 düzeyle sınırlıdır", "new": "Yeni ortam", - "no_active_environment": "No active environment", + "no_active_environment": "Aktif ortam yok", "no_environment": "Ortam yok", "no_environment_description": "Hiçbir ortam seçilmedi. Aşağıdaki değişkenlerle ne yapacağınızı seçin.", "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Ortam seçin", "set": "Set environment", "set_as_environment": "Set as environment", "team_environments": "Team Environments", "title": "Ortamlar", "updated": "Ortam güncellendi", - "value": "Value", - "variable": "Variable", - "variable_list": "Değişken listesi" + "value": "Değer", + "variable": "Değişken", + "variables": "Değişkenler", + "variable_list": "Değişken listesi", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +326,7 @@ "danger_zone": "Danger zone", "delete_account": "Your account is currently an owner in these teams:", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Boş İstek Adı", "f12_details": "(Ayrıntılar için F12)", "gql_prettify_invalid_query": "Geçersiz bir sorgu güzelleştirilemedi, sorgu sözdizimi hatalarını çözüp tekrar deneyin", @@ -297,7 +334,8 @@ "incorrect_email": "Geçersiz e-posta", "invalid_link": "Geçersiz bağlantı", "invalid_link_description": "Tıkladığınız linkin süresi geçmiş veya geçersiz", - "json_parsing_failed": "Invalid JSON", + "invalid_embed_link": "The embed does not exist or is invalid.", + "json_parsing_failed": "Geçersiz JSON", "json_prettify_invalid_body": "Geçersiz bir gövde güzelleştirilemedi, JSON sözdizimi hatalarını çözüp tekrar deneyin", "network_error": "Görünene göre bir ağ hatası var. Lütfen tekrar deneyin.", "network_fail": "İstek gönderilemedi", @@ -308,22 +346,29 @@ "page_not_found": "This page could not be found", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Ön istek komut dosyası çalıştırılamadı", "something_went_wrong": "Bir şeyler yanlış gitti", - "test_script_fail": "Could not execute post-request script" + "test_script_fail": "Could not execute post-request script", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "JSON olarak dışa aktar", "create_secret_gist": "Gizli Gist oluştur", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist oluşturuldu", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Gizli Gist oluşturmak için GitHub ile giriş yapın", - "title": "Dışarı Aktar" + "title": "Dışarı Aktar", + "success": "Successfully exported" }, "filter": { - "all": "All", - "none": "None", - "starred": "Starred" + "all": "Tümü", + "none": "Hiçbiri", + "starred": "Yıldızlı" }, "folder": { "created": "Klasör oluşturuldu", @@ -340,7 +385,8 @@ "mutations": "Mutasyonlar", "schema": "Şema", "subscriptions": "Abonelikler", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +452,17 @@ "json_description": "Import collections from a Hoppscotch Collections JSON file", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "İçe aktar" + "title": "İçe aktar", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +598,7 @@ "raw_body": "Ham istek gövdesi", "rename": "Rename Request", "renamed": "Yeniden adlandırılmış istek", + "request_variables": "Request variables", "run": "Çalıştır", "save": "Kaydet", "save_as": "Farklı kaydet", @@ -558,9 +610,9 @@ "title": "İstek", "type": "İstek türü", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Değişkenler", - "view_my_links": "View my links", - "copy_link": "Bağlantıyı kopyala" + "view_my_links": "View my links" }, "response": { "audio": "Audio", @@ -612,7 +664,7 @@ "profile_description": "Profil detaylarını güncelle", "profile_email": "E-posta adresi", "profile_name": "Profil ismi", - "proxy": "vekil", + "proxy": "Proxy", "proxy_url": "Proxy URL'si", "proxy_use_toggle": "İstek göndermek için proxy ara yazılımını kullanın", "read_the": "Oku", @@ -705,8 +757,7 @@ "send_request": "İstek gönder", "share_request": "Share Request", "show_code": "Generate code snippet", - "title": "İstek", - "copy_request_link": "İstek bağlantısını kopyala" + "title": "İstek" }, "response": { "copy": "Copy response to clipboard", @@ -736,7 +787,7 @@ "url": "Bağlantı" }, "spotlight": { - "change_language": "Change Language", + "change_language": "Dil Değiştir", "environments": { "delete": "Delete current environment", "duplicate": "Duplicate current environment", @@ -745,7 +796,7 @@ "edit_global": "Edit global environment", "new": "Create new environment", "new_variable": "Create a new environment variable", - "title": "Environments" + "title": "Ortamlar" }, "general": { "chat": "Chat with support", @@ -812,6 +863,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,7 +926,6 @@ "forum": "Sorular sorun ve cevaplar alın", "github": "Bizi Github'da takip edin", "shortcuts": "Uygulamaya daha hızlı göz atın", - "team": "Takımla iletişim kurun", "title": "Destek", "twitter": "Bizi Twitter'da takip edin" }, @@ -890,6 +947,9 @@ "query": "Sorgu", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Testler", @@ -918,7 +978,6 @@ "invite_tooltip": "İnsanları bu çalışma alanına davet edin", "invited_to_team": "{owner} seni {team} takımına davet etti.", "join": "Davet kabul edildi", - "join_beta": "Takımlara erişmek için beta programına katılın.", "join_team": "{team}'e katıl", "joined_team": "{team} takımına katıldın", "joined_team_description": "Artık bu takımın bir üyesisin", @@ -951,7 +1010,11 @@ "success_invites": "Success invites", "title": "Başlık", "we_sent_invite_link": "Tüm davetlilere bir davet bağlantısı gönderdik!", - "we_sent_invite_link_description": "Tüm davetlilerden gelen kutularını kontrol etmelerini isteyin. Ekibe katılmak için bağlantıya tıklayın." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Tüm davetlilerden gelen kutularını kontrol etmelerini isteyin. Ekibe katılmak için bağlantıya tıklayın.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests" }, "team_environment": { "deleted": "Environment Deleted", @@ -978,9 +1041,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Actions", "created_on": "Created on", diff --git a/packages/hoppscotch-common/locales/tw.json b/packages/hoppscotch-common/locales/tw.json index a0c82384c2..6bcf8e4c1a 100644 --- a/packages/hoppscotch-common/locales/tw.json +++ b/packages/hoppscotch-common/locales/tw.json @@ -24,8 +24,10 @@ "go_back": "返回", "go_forward": "向前", "group_by": "分組方式", + "hide_secret": "Hide secret", "label": "標籤", "learn_more": "瞭解更多", + "download_here": "Download here", "less": "更少", "more": "更多", "new": "新增", @@ -43,6 +45,7 @@ "search": "搜尋", "send": "傳送", "share": "Share", + "show_secret": "Show secret", "start": "開始", "starting": "正在開始", "stop": "停止", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "輸入命令或搜尋內容……", "we_use_cookies": "我們使用 cookies", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "新功能", + "see_whats_new": "See what’s new", "wiki": "維基" }, "auth": { "account_exists": "帳號存在不同的憑證 - 登入後可連結兩個帳號", "all_sign_in_options": "所有登入選項", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "使用電子信箱登入", "continue_with_github": "使用 GitHub 登入", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "使用 Google 登入", "continue_with_microsoft": "使用 Microsoft 登入", "continue_with_oidc": "使用 OIDC 登入", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "傳遞方式", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "密碼", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "權杖", @@ -150,6 +178,7 @@ "different_parent": "無法為父集合不同的集合重新排序", "edit": "編輯集合", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "請提供有效的集合名稱", "invalid_root_move": "集合已在根目錄", "moved": "移動成功", @@ -166,6 +195,7 @@ "save_to_collection": "儲存到集合", "select": "選擇一個集合", "select_location": "選擇位置", + "details": "Details", "select_team": "選擇一個團隊", "team_collections": "團隊集合" }, @@ -184,7 +214,8 @@ "remove_telemetry": "您確定要退出遙測服務嗎?", "request_change": "您確定要捨棄目前的請求嗎?未儲存的變更將遺失。", "save_unsaved_tab": "您要儲存在此分頁做出的改動嗎?", - "sync": "您想從雲端恢復您的工作區嗎?這將丟棄您的本地進度。" + "sync": "您想從雲端恢復您的工作區嗎?這將丟棄您的本地進度。", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "新增至參數", @@ -238,13 +269,16 @@ "pending_invites": "這個團隊沒有待定的邀請", "profile": "登入以檢視您的設定檔", "protocols": "協定為空", + "request_variables": "This request does not have any request variables", "schema": "連線至 GraphQL 端點", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "訂閱為空", "team_name": "團隊名稱為空", "teams": "團隊為空", "tests": "沒有針對該請求的測試", + "access_tokens": "Access tokens are empty", "shortcodes": "Shortcodes 為空" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "快速預覽環境", "replace_with_variable": "以變數替代", "scope": "範圍", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "選擇環境", "set": "設定環境", "set_as_environment": "設為環境", @@ -279,7 +315,10 @@ "updated": "更新環境", "value": "數值", "variable": "變數", - "variable_list": "變數列表" + "variables": "Variables", + "variable_list": "變數列表", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "危險地帶", "delete_account": "您的帳號目前為這些團隊的擁有者:", "delete_account_description": "您在刪除帳號前必須先將您自己從團隊中移除、轉移擁有權,或是刪除團隊。", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "空請求名稱", "f12_details": "(按下 F12 以獲悉詳情)", "gql_prettify_invalid_query": "無法美化無效的查詢,處理查詢語法錯誤並重試", @@ -297,6 +337,7 @@ "incorrect_email": "錯誤的電子信箱", "invalid_link": "連結無效", "invalid_link_description": "您點擊的連結無效或已過期。", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "JSON 無效", "json_prettify_invalid_body": "無法美化無效的請求主體,處理 JSON 語法錯誤並重試", "network_error": "似乎有網路錯誤。請再試一次。", @@ -308,17 +349,25 @@ "page_not_found": "找不到此頁面", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy 錯誤", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "無法執行預請求指令碼", "something_went_wrong": "發生了一些錯誤", - "test_script_fail": "無法執行測試指令碼" + "test_script_fail": "無法執行測試指令碼", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "匯出為 JSON", "create_secret_gist": "建立私密 Gist", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "已建立 Gist", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "使用 GitHub 登入以建立私密 Gist", - "title": "匯出" + "title": "匯出", + "success": "Successfully exported", + "gist_created": "已建立 Gist" }, "filter": { "all": "全部", @@ -340,7 +389,8 @@ "mutations": "變體", "schema": "綱要", "subscriptions": "訂閱", - "switch_connection": "切換連線" + "switch_connection": "切換連線", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "從 Hoppscotch 集合 JSON 檔匯入集合", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "匯入" + "title": "匯入", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "檢查潛在錯誤", "environment": { "add_environment": "新增至環境", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "找不到環境變數 “{environment}”。" }, "header": { @@ -547,6 +602,7 @@ "raw_body": "原始請求本體", "rename": "重新命名請求", "renamed": "請求已重新命名", + "request_variables": "Request variables", "run": "執行", "save": "儲存", "save_as": "另存為", @@ -558,6 +614,7 @@ "title": "請求", "type": "請求類型", "url": "網址", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "變數", "view_my_links": "檢視我的連結", "copy_link": "複製連結" @@ -812,6 +869,13 @@ "new": "建立新團隊", "switch_to_personal": "切換至您的個人工作區", "title": "團隊" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "答疑解惑", "github": "在 GitHub 關注我們", "shortcuts": "更快瀏覽應用程式", - "team": "與團隊保持聯絡", "title": "支援", - "twitter": "在 Twitter 關注我們" + "twitter": "在 Twitter 關注我們", + "team": "與團隊保持聯絡" }, "tab": { "authorization": "授權", @@ -890,6 +954,9 @@ "query": "查詢", "schema": "綱要", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "測試", @@ -918,7 +985,6 @@ "invite_tooltip": "邀請他人到這個工作區", "invited_to_team": "{owner} 邀請您加入 {team}", "join": "已接受邀請", - "join_beta": "加入 Beta 計畫以存取團隊。", "join_team": "加入 {team}", "joined_team": "您已加入 {team}", "joined_team_description": "您現在是這個團隊的成員", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "團隊", "we_sent_invite_link": "我們向所有受邀者傳送了邀請連結!", - "we_sent_invite_link_description": "請所有受邀者檢查他們的收件匣。點擊連結加入團隊。" + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "請所有受邀者檢查他們的收件匣。點擊連結加入團隊。", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "加入 Beta 計畫以存取團隊。" }, "team_environment": { "deleted": "已刪除環境", @@ -978,9 +1049,50 @@ "workspace": { "change": "切換工作區", "personal": "我的工作區", + "other_workspaces": "My Workspaces", "team": "團隊工作區", "title": "工作區" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "操作", "created_on": "建立於", diff --git a/packages/hoppscotch-common/locales/uk.json b/packages/hoppscotch-common/locales/uk.json index 6f4d73e8d7..915bc0f3f6 100644 --- a/packages/hoppscotch-common/locales/uk.json +++ b/packages/hoppscotch-common/locales/uk.json @@ -24,8 +24,10 @@ "go_back": "Повернутись", "go_forward": "Go forward", "group_by": "Групувати за", + "hide_secret": "Hide secret", "label": "Мітка", "learn_more": "Дізнатись більше", + "download_here": "Download here", "less": "Менше", "more": "Більше", "new": "Новий", @@ -43,6 +45,7 @@ "search": "Пошук", "send": "Надіслати", "share": "Share", + "show_secret": "Show secret", "start": "Почати", "starting": "Розпочинається", "stop": "Зупити", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Введіть команду або виконайте пошук…", "we_use_cookies": "Ми використовуємо файли cookie", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Що нового?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Обліковий запис існує з різними обліковими даними - увійдіть, щоб зв'язати обидва облікові записи", "all_sign_in_options": "Усі параметри входу", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Продовжити з електронною поштою", "continue_with_github": "Продовжити з GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Продовжити з Google", "continue_with_microsoft": "Продовжити з Microsoft", "continue_with_oidc": "Продовжити з OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Пропустити", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Пароль", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Токен", @@ -150,6 +178,7 @@ "different_parent": "Cannot reorder collection with different parent", "edit": "Редагувати колекцію", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Укажіть дійсну назву колекції", "invalid_root_move": "Collection already in the root", "moved": "Moved Successfully", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Виберіть колекцію", "select_location": "Виберіть місце розташування", + "details": "Details", "select_team": "Виберіть команду", "team_collections": "Колекції команд" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Ви впевнені, що хочете відмовитися від телеметрії?", "request_change": "Ви дійсно бажаєте скасувати поточний запит? Незбережені зміни будуть втрачені.", "save_unsaved_tab": "Do you want to save changes made in this tab?", - "sync": "Ви впевнені, що хочете синхронізувати цю робочу область?" + "sync": "Ви впевнені, що хочете синхронізувати цю робочу область?", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "Немає запрошень в очікуванні для цієї команди", "profile": "Увійдіть для перегляду вашого профілю", "protocols": "Протоколи порожні", + "request_variables": "This request does not have any request variables", "schema": "Підключіться до кінцевої точки GraphQL", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Підписки порожні", "team_name": "Назва команди порожня", "teams": "Команди порожні", "tests": "Для цього запиту немає тестів", + "access_tokens": "Access tokens are empty", "shortcodes": "Короткі коди порожні" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Виберіть середовище", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Оновлення середовища", "value": "Value", "variable": "Variable", - "variable_list": "Список змінних" + "variables": "Variables", + "variable_list": "Список змінних", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Небезпечна зона", "delete_account": "Ваш обліковий запис на разі володіє цими командами:", "delete_account_description": "Ви повинні або видалити себе, або передати право власності, або видалити ці команди, перш ніж ви зможете видалити свій обліковий запис.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Пуста назва запиту", "f12_details": "(F12 для деталей)", "gql_prettify_invalid_query": "Не вдалося попередньо визначити недійсний запит, вирішити синтаксичні помилки запиту та повторити спробу", @@ -297,6 +337,7 @@ "incorrect_email": "Невірна електронна адреса", "invalid_link": "Невірне посилання", "invalid_link_description": "Посилання, яке ви натиснули, є недійсним або застарілим.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "Неправильний JSON", "json_prettify_invalid_body": "Не вдалося заздалегідь визначити недійсне тіло, вирішити синтаксичні помилки json і повторити спробу", "network_error": "Здається, виникла мережева помилка. Будь ласка, спробуйте ще раз.", @@ -308,17 +349,25 @@ "page_not_found": "Ця сторінка не знайдена", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Не вдалося виконати сценарій попереднього запиту", "something_went_wrong": "Щось пішло не так", - "test_script_fail": "Не вдалося виконати скрипт після запиту" + "test_script_fail": "Не вдалося виконати скрипт після запиту", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Експортувати як JSON", "create_secret_gist": "Створити секретний GitHub Gist", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist створений", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Увійдіть за допомогою GitHub, щоб створити секретний Gist", - "title": "Експортувати" + "title": "Експортувати", + "success": "Successfully exported", + "gist_created": "Gist створений" }, "filter": { "all": "Всі", @@ -340,7 +389,8 @@ "mutations": "Мутації", "schema": "Схема", "subscriptions": "Підписки", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Імпортувати колекції з колекцій Hoppscotch JSON файлу", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Імпортувати" + "title": "Імпортувати", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Сировина запиту", "rename": "Rename Request", "renamed": "Запит перейменовано", + "request_variables": "Request variables", "run": "Біжи", "save": "Зберегти", "save_as": "Зберегти як", @@ -558,6 +614,7 @@ "title": "Запит", "type": "Тип запиту", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Змінні", "view_my_links": "Переглянути мої посилання", "copy_link": "Скопіювати посилання" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Задавайте питання і отримуйте відповіді", "github": "Слідкуйте за нами на Github", "shortcuts": "Швидше переглядайте програми", - "team": "Зв'яжіться з командою", "title": "Підтримка", - "twitter": "Слідкуйте за нами у Twitter" + "twitter": "Слідкуйте за нами у Twitter", + "team": "Зв'яжіться з командою" }, "tab": { "authorization": "Авторизація", @@ -890,6 +954,9 @@ "query": "Запит", "schema": "Схема", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Тести", @@ -918,7 +985,6 @@ "invite_tooltip": "Запросити людей в це робоче середовище", "invited_to_team": "{owner} запросив вас приєднатися до {team}", "join": "Запрошення прийнято", - "join_beta": "Приєднуйтесь до бета -програми, щоб отримати доступ до команд.", "join_team": "Приєднатися до {team}", "joined_team": "Ви приєдналися до {team}", "joined_team_description": "Ви тепер учасник цієї команди", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Команди", "we_sent_invite_link": "Ми надіслали запрошення всім запрошеним!", - "we_sent_invite_link_description": "Попросіть всіх запрошених перевірити свої поштові скриньки. Перейдіть за посиланням, щоб приєднатися до команди." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Попросіть всіх запрошених перевірити свої поштові скриньки. Перейдіть за посиланням, щоб приєднатися до команди.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Приєднуйтесь до бета -програми, щоб отримати доступ до команд." }, "team_environment": { "deleted": "Середовище видалено", @@ -978,9 +1049,50 @@ "workspace": { "change": "Change workspace", "personal": "My Workspace", + "other_workspaces": "My Workspaces", "team": "Team Workspace", "title": "Workspaces" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Дії", "created_on": "Дата створення", diff --git a/packages/hoppscotch-common/locales/vi.json b/packages/hoppscotch-common/locales/vi.json index 7eeaff6e2e..0285b056c1 100644 --- a/packages/hoppscotch-common/locales/vi.json +++ b/packages/hoppscotch-common/locales/vi.json @@ -24,8 +24,10 @@ "go_back": "Quay lại", "go_forward": "Tiến về phía trước", "group_by": "Nhóm theo", + "hide_secret": "Hide secret", "label": "Nhãn", "learn_more": "Tìm hiểu thêm", + "download_here": "Download here", "less": "Ít hơn", "more": "Thêm", "new": "Mới", @@ -43,6 +45,7 @@ "search": "Tìm kiếm", "send": "Gửi", "share": "Share", + "show_secret": "Show secret", "start": "Bắt đầu", "starting": "Đang bắt đầu", "stop": "Dừng", @@ -95,14 +98,18 @@ "twitter": "Twitter", "type_a_command_search": "Nhập một lệnh hoặc tìm kiếm...", "we_use_cookies": "Chúng tôi sử dụng cookie", + "updated_text": "Hoppscotch has been updated to v{version} 🎉", "whats_new": "Có gì mới?", + "see_whats_new": "See what’s new", "wiki": "Wiki" }, "auth": { "account_exists": "Tài khoản đã tồn tại với thông tin đăng nhập khác - Đăng nhập để liên kết hai tài khoản", "all_sign_in_options": "Tất cả các tùy chọn đăng nhập", + "continue_with_auth_provider": "Continue with {provider}", "continue_with_email": "Tiếp tục bằng Email", "continue_with_github": "Tiếp tục bằng GitHub", + "continue_with_github_enterprise": "Continue with GitHub Enterprise", "continue_with_google": "Tiếp tục bằng Google", "continue_with_microsoft": "Tiếp tục bằng Microsoft", "continue_with_oidc": "Tiếp tục bằng OIDC", @@ -136,9 +143,30 @@ "redirect_no_token_endpoint": "No Token Endpoint Defined", "something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect", "something_went_wrong_on_token_generation": "Something went wrong on token generation", - "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed" + "token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed", + "grant_type": "Grant Type", + "grant_type_auth_code": "Authorization Code", + "token_fetched_successfully": "Token fetched successfully", + "token_fetch_failed": "Failed to fetch token", + "validation_failed": "Validation Failed, please check the form fields", + "label_authorization_endpoint": "Authorization Endpoint", + "label_client_id": "Client ID", + "label_client_secret": "Client Secret", + "label_code_challenge": "Code Challenge", + "label_code_challenge_method": "Code Challenge Method", + "label_code_verifier": "Code Verifier", + "label_scopes": "Scopes", + "label_token_endpoint": "Token Endpoint", + "label_use_pkce": "Use PKCE", + "label_implicit": "Implicit", + "label_password": "Password", + "label_username": "Username", + "label_auth_code": "Authorization Code", + "label_client_credentials": "Client Credentials" }, "pass_key_by": "Truyền qua", + "pass_by_query_params_label": "Query Parameters", + "pass_by_headers_label": "Headers", "password": "Mật khẩu", "save_to_inherit": "Please save this request in any collection to inherit the authorization", "token": "Token", @@ -150,6 +178,7 @@ "different_parent": "Không thể sắp xếp lại bộ sưu tập với cha khác", "edit": "Chỉnh sửa bộ sưu tập", "import_or_create": "Import or create a collection", + "import_collection": "Import Collection", "invalid_name": "Vui lòng cung cấp tên cho bộ sưu tập", "invalid_root_move": "Bộ sưu tập đã nằm trong thư mục gốc", "moved": "Di chuyển thành công", @@ -166,6 +195,7 @@ "save_to_collection": "Save to Collection", "select": "Chọn một bộ sưu tập", "select_location": "Chọn vị trí", + "details": "Details", "select_team": "Chọn một nhóm", "team_collections": "Bộ sưu tập của nhóm" }, @@ -184,7 +214,8 @@ "remove_telemetry": "Bạn có chắc chắn muốn tắt Telemetry?", "request_change": "Bạn có chắc chắn muốn hủy request hiện tại? Những thay đổi chưa được lưu sẽ bị mất.", "save_unsaved_tab": "Bạn có muốn lưu các thay đổi đã được thực hiện trong tab này?", - "sync": "Bạn có muốn khôi phục không gian làm việc từ đám mây? Điều này sẽ xóa bỏ tiến trình địa phương của bạn." + "sync": "Bạn có muốn khôi phục không gian làm việc từ đám mây? Điều này sẽ xóa bỏ tiến trình địa phương của bạn.", + "delete_access_token": "Are you sure you want to delete the access token {tokenLabel}?" }, "context_menu": { "add_parameters": "Add to parameters", @@ -238,13 +269,16 @@ "pending_invites": "Không có lời mời đang chờ cho nhóm này", "profile": "Đăng nhập để xem hồ sơ của bạn", "protocols": "Danh sách giao thức trống rỗng", + "request_variables": "This request does not have any request variables", "schema": "Kết nối với điểm cuối GraphQL để xem lược đồ", + "secret_environments": "Secrets are not synced to Hoppscotch", "shared_requests": "Shared requests are empty", "shared_requests_logout": "Login to view your shared requests or create a new one", "subscription": "Danh sách đăng ký trống rỗng", "team_name": "Tên nhóm trống rỗng", "teams": "Bạn không thuộc bất kỳ nhóm nào", "tests": "Không có bài kiểm tra cho request này", + "access_tokens": "Access tokens are empty", "shortcodes": "Danh sách mã ngắn trống rỗng" }, "environment": { @@ -271,6 +305,8 @@ "quick_peek": "Environment Quick Peek", "replace_with_variable": "Replace with variable", "scope": "Scope", + "secrets": "Secrets", + "secret_value": "Secret value", "select": "Chọn môi trường", "set": "Set environment", "set_as_environment": "Set as environment", @@ -279,7 +315,10 @@ "updated": "Cập nhật môi trường thành công", "value": "Value", "variable": "Variable", - "variable_list": "Danh sách biến" + "variables": "Variables", + "variable_list": "Danh sách biến", + "properties": "Environment Properties", + "details": "Details" }, "error": { "authproviders_load_error": "Unable to load auth providers", @@ -290,6 +329,7 @@ "danger_zone": "Vùng nguy hiểm", "delete_account": "Tài khoản của bạn hiện là chủ sở hữu trong các nhóm sau:", "delete_account_description": "Bạn phải xóa bản thân, chuyển quyền sở hữu hoặc xóa các nhóm này trước khi bạn có thể xóa tài khoản của mình.", + "empty_profile_name": "Profile name cannot be empty", "empty_req_name": "Tên request trống", "f12_details": "(Nhấn F12 để xem chi tiết)", "gql_prettify_invalid_query": "Không thể định dạng đẹp một truy vấn không hợp lệ, giải quyết lỗi cú pháp truy vấn và thử lại", @@ -297,6 +337,7 @@ "incorrect_email": "Email không chính xác", "invalid_link": "Liên kết không hợp lệ", "invalid_link_description": "Liên kết bạn đã nhấp vào không hợp lệ hoặc đã hết hạn.", + "invalid_embed_link": "The embed does not exist or is invalid.", "json_parsing_failed": "JSON không hợp lệ", "json_prettify_invalid_body": "Không thể định dạng đẹp một phần thân không hợp lệ, giải quyết lỗi cú pháp JSON và thử lại", "network_error": "Có vẻ như có lỗi mạng. Vui lòng thử lại.", @@ -308,17 +349,25 @@ "page_not_found": "Không tìm thấy trang này", "please_install_extension": "Please install the extension and add origin to the extension.", "proxy_error": "Proxy error", + "same_profile_name": "Updated profile name is same as the current profile name", "script_fail": "Không thể thực thi kịch bản trước request", "something_went_wrong": "Đã xảy ra lỗi", - "test_script_fail": "Không thể thực thi kịch bản sau request" + "test_script_fail": "Không thể thực thi kịch bản sau request", + "reading_files": "Error while reading one or more files.", + "fetching_access_tokens_list": "Something went wrong while fetching the list of tokens", + "generate_access_token": "Something went wrong while generating the access token", + "delete_access_token": "Something went wrong while deleting the access token" }, "export": { "as_json": "Xuất dưới dạng JSON", "create_secret_gist": "Tạo Gist bí mật", + "create_secret_gist_tooltip_text": "Export as secret Gist", "failed": "Something went wrong while exporting", - "gist_created": "Gist đã được tạo", + "secret_gist_success": "Successfully exported as secret Gist", "require_github": "Đăng nhập bằng GitHub để tạo Gist bí mật", - "title": "Xuất" + "title": "Xuất", + "success": "Successfully exported", + "gist_created": "Gist đã được tạo" }, "filter": { "all": "Tất cả", @@ -340,7 +389,8 @@ "mutations": "Mutations", "schema": "Schema", "subscriptions": "Subscriptions", - "switch_connection": "Switch connection" + "switch_connection": "Switch connection", + "url_placeholder": "Enter a GraphQL endpoint URL" }, "graphql_collections": { "title": "GraphQL Collections" @@ -406,12 +456,17 @@ "json_description": "Nhập bộ sưu tập từ tệp JSON Hoppscotch", "postman_environment": "Postman Environment", "postman_environment_description": "Import Postman Environment from a JSON file", - "title": "Nhập" + "title": "Nhập", + "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported", + "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.", + "success": "Successfully imported" }, "inspections": { "description": "Inspect possible errors", "environment": { "add_environment": "Add to Environment", + "add_environment_value": "Add value", + "empty_value": "Environment value is empty for the variable '{variable}' ", "not_found": "Environment variable “{environment}” not found." }, "header": { @@ -547,6 +602,7 @@ "raw_body": "Nội dung thô", "rename": "Rename Request", "renamed": "Đã đổi tên", + "request_variables": "Request variables", "run": "Chạy", "save": "Lưu", "save_as": "Lưu như", @@ -558,6 +614,7 @@ "title": "Request", "type": "Loại request", "url": "URL", + "url_placeholder": "Enter a URL or paste a cURL command", "variables": "Biến", "view_my_links": "Xem các liên kết của tôi", "copy_link": "Sao chép liên kết" @@ -812,6 +869,13 @@ "new": "Create new team", "switch_to_personal": "Switch to your personal workspace", "title": "Teams" + }, + "phrases": { + "try": "Try", + "import_collections": "Import collections", + "create_environment": "Create environment", + "create_workspace": "Create workspace", + "share_request": "Share request" } }, "sse": { @@ -868,9 +932,9 @@ "forum": "Hỏi câu hỏi và nhận câu trả lời", "github": "Theo dõi chúng tôi trên Github", "shortcuts": "Duyệt ứng dụng nhanh hơn", - "team": "Liên hệ với nhóm", "title": "Hỗ trợ", - "twitter": "Theo dõi chúng tôi trên Twitter" + "twitter": "Theo dõi chúng tôi trên Twitter", + "team": "Liên hệ với nhóm" }, "tab": { "authorization": "Xác thực", @@ -890,6 +954,9 @@ "query": "Truy vấn", "schema": "Schema", "shared_requests": "Shared Requests", + "codegen": "Generate Code", + "code_snippet": "Code snippet", + "share_tab_request": "Share tab request", "socketio": "Socket.IO", "sse": "SSE", "tests": "Kiểm tra", @@ -918,7 +985,6 @@ "invite_tooltip": "Mời người khác vào không gian làm việc này", "invited_to_team": "{owner} đã mời bạn tham gia {team}", "join": "Đã chấp nhận lời mời", - "join_beta": "Tham gia chương trình beta để truy cập vào nhóm.", "join_team": "Tham gia {team}", "joined_team": "Bạn đã tham gia {team}", "joined_team_description": "Bạn hiện là thành viên của nhóm này", @@ -951,7 +1017,12 @@ "success_invites": "Success invites", "title": "Nhóm", "we_sent_invite_link": "Chúng tôi đã gửi một liên kết mời đến tất cả người được mời!", - "we_sent_invite_link_description": "Yêu cầu tất cả người được mời kiểm tra hộp thư đến của họ. Nhấp vào liên kết để tham gia nhóm." + "invite_sent_smtp_disabled": "Invite links generated", + "we_sent_invite_link_description": "Yêu cầu tất cả người được mời kiểm tra hộp thư đến của họ. Nhấp vào liên kết để tham gia nhóm.", + "invite_sent_smtp_disabled_description": "Sending invite emails is disabled for this instance of Hoppscotch. Please use the Copy link button to copy and share the invite link manually.", + "copy_invite_link": "Copy Invite Link", + "search_title": "Team Requests", + "join_beta": "Tham gia chương trình beta để truy cập vào nhóm." }, "team_environment": { "deleted": "Môi trường đã bị xóa", @@ -978,9 +1049,50 @@ "workspace": { "change": "Thay đổi không gian làm việc", "personal": "Không gian làm việc của tôi", + "other_workspaces": "My Workspaces", "team": "Không gian làm việc nhóm", "title": "Không gian làm việc" }, + "site_protection": { + "login_to_continue": "Login to continue", + "login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.", + "error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status" + }, + "access_tokens": { + "tab_title": "Tokens", + "section_title": "Personal Access Tokens", + "section_description": "Personal access tokens currently helps you connect the CLI to your Hoppscotch account", + "last_used_on": "Last used on", + "expires_on": "Expires on", + "no_expiration": "No expiration", + "expired": "Expired", + "copy_token_warning": "Make sure to copy your personal access token now. You won't be able to see it again!", + "token_purpose": "What's this token for?", + "expiration_label": "Expiration", + "scope_label": "Scope", + "workspace_read_only_access": "Read-only access to workspace data.", + "personal_workspace_access_limitation": "Personal Access Tokens can't access your personal workspace.", + "generate_token": "Generate Token", + "invalid_label": "Please provide a label for the token", + "no_expiration_verbose": "This token will never expire!", + "token_expires_on": "This token will expire on", + "generate_new_token": "Generate new token", + "generate_modal_title": "New Personal Access Token", + "deletion_success": "The access token {label} has been deleted" + }, + "collection_runner": { + "collection_id": "Collection ID", + "environment_id": "Environment ID", + "cli_collection_id_description": "This collection ID will be used by the CLI collection runner for Hoppscotch.", + "cli_environment_id_description": "This environment ID will be used by the CLI collection runner for Hoppscotch.", + "include_active_environment": "Include active environment:", + "cli": "CLI", + "ui": "Runner (coming soon)", + "cli_command_generation_description_cloud": "Copy the below command and run it from the CLI. Please specify a personal access token.", + "cli_command_generation_description_sh": "Copy the below command and run it from the CLI. Please specify a personal access token and verify the generated SH instance server URL.", + "cli_command_generation_description_sh_with_server_url_placeholder": "Copy the below command and run it from the CLI. Please specify a personal access token and the SH instance server URL.", + "run_collection": "Run collection" + }, "shortcodes": { "actions": "Hành động", "created_on": "Được tạo vào", diff --git a/packages/hoppscotch-common/package.json b/packages/hoppscotch-common/package.json index 195741c4d0..f705ab244d 100644 --- a/packages/hoppscotch-common/package.json +++ b/packages/hoppscotch-common/package.json @@ -1,7 +1,7 @@ { "name": "@hoppscotch/common", "private": true, - "version": "2024.3.0", + "version": "2024.12.0", "scripts": { "dev": "pnpm exec npm-run-all -p -l dev:*", "test": "vitest --run", @@ -22,63 +22,68 @@ }, "dependencies": { "@apidevtools/swagger-parser": "10.1.0", - "@codemirror/autocomplete": "6.13.0", - "@codemirror/commands": "6.3.3", + "@codemirror/autocomplete": "6.18.1", + "@codemirror/commands": "6.7.0", "@codemirror/lang-javascript": "6.2.2", "@codemirror/lang-json": "6.0.1", "@codemirror/lang-xml": "6.1.0", "@codemirror/language": "6.10.1", - "@codemirror/legacy-modes": "6.3.3", - "@codemirror/lint": "6.5.0", + "@codemirror/legacy-modes": "6.4.1", + "@codemirror/lint": "6.8.2", + "@codemirror/merge": "6.7.2", "@codemirror/search": "6.5.6", "@codemirror/state": "6.4.1", "@codemirror/view": "6.25.1", "@hoppscotch/codemirror-lang-graphql": "workspace:^", "@hoppscotch/data": "workspace:^", + "@hoppscotch/httpsnippet": "3.0.6", "@hoppscotch/js-sandbox": "workspace:^", - "@hoppscotch/ui": "0.1.0", + "@hoppscotch/ui": "0.2.2", "@hoppscotch/vue-toasted": "0.1.0", "@lezer/highlight": "1.2.0", - "@unhead/vue": "1.8.8", - "@urql/core": "4.2.0", + "@noble/curves": "1.6.0", + "@scure/base": "1.1.9", + "@shopify/lang-jsonc": "1.0.0", + "@unhead/vue": "1.11.10", + "@urql/core": "5.0.6", "@urql/devtools": "2.0.3", - "@urql/exchange-auth": "2.1.6", - "@urql/exchange-graphcache": "6.4.0", - "@vitejs/plugin-legacy": "4.1.1", - "@vueuse/core": "10.7.0", - "acorn-walk": "8.3.0", - "axios": "1.6.2", + "@urql/exchange-auth": "2.2.0", + "@vueuse/core": "11.1.0", + "acorn-walk": "8.3.4", + "aws4fetch": "1.0.20", + "axios": "1.7.7", "buffer": "6.0.3", - "cookie-es": "1.0.0", - "dioc": "1.0.1", + "cookie-es": "1.2.2", + "dioc": "3.0.2", "esprima": "4.0.1", "events": "3.3.0", - "fp-ts": "2.16.1", - "globalthis": "1.0.3", - "graphql": "16.8.1", + "fp-ts": "2.16.9", + "globalthis": "1.0.4", + "graphql": "16.9.0", "graphql-language-service-interface": "2.10.2", "graphql-tag": "2.12.6", - "httpsnippet": "3.0.1", "insomnia-importers": "3.6.0", - "io-ts": "2.2.20", + "io-ts": "2.2.21", + "js-md5": "0.8.3", "js-yaml": "4.1.0", - "jsonpath-plus": "7.2.0", + "jsonc-parser": "3.3.1", + "jsonpath-plus": "10.0.0", "lodash-es": "4.17.21", - "lossless-json": "3.0.2", - "minisearch": "6.3.0", + "lossless-json": "4.0.2", + "minisearch": "7.1.0", "nprogress": "0.2.0", "paho-mqtt": "1.1.0", "path": "0.12.7", - "postman-collection": "4.3.0", + "postman-collection": "4.5.0", "process": "0.11.10", - "qs": "6.11.2", - "quicktype-core": "23.0.79", + "qs": "6.13.0", + "quicktype-core": "23.0.170", "rxjs": "7.8.1", - "set-cookie-parser": "2.6.0", + "set-cookie-parser": "2.7.0", "set-cookie-parser-es": "1.0.5", - "socket.io-client-v2": "npm:socket.io-client@2.4.0", + "socket.io-client-v2": "npm:socket.io-client@2.5.0", "socket.io-client-v3": "npm:socket.io-client@3.1.3", - "socket.io-client-v4": "npm:socket.io-client@4.4.1", + "socket.io-client-v4": "npm:socket.io-client@4.8.0", "socketio-wildcard": "2.0.0", "splitpanes": "3.1.5", "stream-browserify": "3.0.0", @@ -86,82 +91,84 @@ "tern": "0.24.3", "timers": "0.1.1", "tippy.js": "6.3.7", - "url": "0.11.3", + "url": "0.11.4", "util": "0.12.5", - "uuid": "9.0.1", - "verzod": "0.2.2", - "vue": "3.3.9", - "vue-i18n": "9.8.0", - "vue-pdf-embed": "1.2.1", - "vue-router": "4.2.5", - "vue-tippy": "6.3.1", + "uuid": "10.0.0", + "verzod": "0.2.3", + "vue": "3.5.12", + "vue-i18n": "10.0.4", + "vue-pdf-embed": "2.1.0", + "vue-router": "4.4.5", + "vue-tippy": "6.5.0", "vuedraggable-es": "4.1.1", "wonka": "6.3.4", - "workbox-window": "7.0.0", - "xml-formatter": "3.6.0", + "workbox-window": "7.1.0", + "xml-formatter": "3.6.3", "yargs-parser": "21.1.1", - "zod": "3.22.4" + "zod": "3.23.8" }, "devDependencies": { "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", - "@graphql-codegen/add": "5.0.0", - "@graphql-codegen/cli": "5.0.0", - "@graphql-codegen/typed-document-node": "5.0.1", - "@graphql-codegen/typescript": "4.0.1", - "@graphql-codegen/typescript-operations": "4.0.1", - "@graphql-codegen/typescript-urql-graphcache": "3.0.0", + "@graphql-codegen/add": "5.0.3", + "@graphql-codegen/cli": "5.0.3", + "@graphql-codegen/typed-document-node": "5.0.10", + "@graphql-codegen/typescript": "4.1.0", + "@graphql-codegen/typescript-operations": "4.3.0", + "@graphql-codegen/typescript-urql-graphcache": "3.1.0", "@graphql-codegen/urql-introspection": "3.0.0", "@graphql-typed-document-node/core": "3.2.0", - "@iconify-json/lucide": "1.1.144", + "@iconify-json/lucide": "1.2.9", "@intlify/vite-plugin-vue-i18n": "7.0.0", "@relmify/jest-fp-ts": "2.1.1", - "@rushstack/eslint-patch": "1.6.0", - "@types/har-format": "1.2.15", + "@rushstack/eslint-patch": "1.10.4", + "@types/har-format": "1.2.16", "@types/js-yaml": "4.0.9", "@types/lodash-es": "4.17.12", "@types/lossless-json": "1.0.4", "@types/nprogress": "0.2.3", "@types/paho-mqtt": "1.0.10", "@types/postman-collection": "3.5.10", + "@types/qs": "6.9.16", "@types/splitpanes": "2.2.6", - "@types/uuid": "9.0.7", + "@types/uuid": "10.0.0", "@types/yargs-parser": "21.0.3", - "@typescript-eslint/eslint-plugin": "7.3.1", - "@typescript-eslint/parser": "7.3.1", - "@vitejs/plugin-vue": "4.5.1", - "@vue/compiler-sfc": "3.3.10", - "@vue/eslint-config-typescript": "12.0.0", - "@vue/runtime-core": "3.3.10", - "autoprefixer": "10.4.16", + "@typescript-eslint/eslint-plugin": "8.9.0", + "@typescript-eslint/parser": "8.9.0", + "@urql/exchange-graphcache": "6.5.1", + "@vitejs/plugin-vue": "5.1.4", + "@vue/compiler-sfc": "3.5.12", + "@vue/eslint-config-typescript": "13.0.0", + "@vue/runtime-core": "3.5.12", + "autoprefixer": "10.4.20", "cross-env": "7.0.3", - "dotenv": "16.3.1", + "dotenv": "16.4.5", "eslint": "8.57.0", - "eslint-plugin-prettier": "5.1.3", - "eslint-plugin-vue": "9.24.0", - "glob": "10.3.10", + "eslint-plugin-prettier": "5.2.1", + "eslint-plugin-vue": "9.29.0", + "glob": "11.0.0", + "jsdom": "25.0.1", "npm-run-all": "4.1.5", "openapi-types": "12.1.3", - "postcss": "8.4.31", - "prettier": "3.1.0", - "prettier-plugin-tailwindcss": "0.5.7", + "postcss": "8.4.47", + "prettier": "3.3.3", + "prettier-plugin-tailwindcss": "0.6.8", "rollup-plugin-polyfill-node": "0.13.0", - "sass": "1.69.5", - "tailwindcss": "3.3.5", - "typescript": "5.3.2", + "sass": "1.79.5", + "tailwindcss": "3.4.14", + "typescript": "5.3.3", "unplugin-fonts": "1.1.1", - "unplugin-icons": "0.17.4", - "unplugin-vue-components": "0.25.2", - "vite": "4.5.0", - "vite-plugin-checker": "0.6.2", + "unplugin-icons": "0.19.3", + "unplugin-vue-components": "0.27.4", + "vite": "5.4.9", + "vite-plugin-checker": "0.6.4", "vite-plugin-fonts": "0.7.0", - "vite-plugin-html-config": "1.0.11", - "vite-plugin-inspect": "0.7.42", - "vite-plugin-pages": "0.31.0", - "vite-plugin-pages-sitemap": "1.6.1", - "vite-plugin-pwa": "0.17.3", - "vite-plugin-vue-layouts": "0.8.0", - "vitest": "0.34.6", + "vite-plugin-html-config": "2.0.2", + "vite-plugin-pages": "0.32.3", + "vite-plugin-pages-sitemap": "1.7.1", + "vite-plugin-pwa": "0.20.5", + "vite-plugin-vue-layouts": "0.11.0", + "vitest": "2.1.3", "vue-tsc": "1.8.24" } } diff --git a/packages/hoppscotch-common/src/App.vue b/packages/hoppscotch-common/src/App.vue index 47a086935d..8d9d7e4d16 100644 --- a/packages/hoppscotch-common/src/App.vue +++ b/packages/hoppscotch-common/src/App.vue @@ -8,6 +8,7 @@ + @@ -19,6 +20,7 @@ import { isLoadingInitialRoute } from "@modules/router" import { useI18n } from "@composables/i18n" import { APP_IS_IN_DEV_MODE } from "@helpers/dev" import { platform } from "./platform" +import { Toaster } from "@hoppscotch/ui" const t = useI18n() diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index 75b5ec66eb..109b1ced7c 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -1,216 +1,318 @@ /* eslint-disable */ -/* prettier-ignore */ // @ts-nocheck // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 -import "@vue/runtime-core" - export {} -declare module "@vue/runtime-core" { +/* prettier-ignore */ +declare module 'vue' { export interface GlobalComponents { - AppActionHandler: (typeof import("./components/app/ActionHandler.vue"))["default"] - AppBanner: (typeof import("./components/app/Banner.vue"))["default"] - AppContextMenu: (typeof import("./components/app/ContextMenu.vue"))["default"] - AppDeveloperOptions: (typeof import("./components/app/DeveloperOptions.vue"))["default"] - AppFooter: (typeof import("./components/app/Footer.vue"))["default"] - AppGitHubStarButton: (typeof import("./components/app/GitHubStarButton.vue"))["default"] - AppHeader: (typeof import("./components/app/Header.vue"))["default"] - AppInspection: (typeof import("./components/app/Inspection.vue"))["default"] - AppInterceptor: (typeof import("./components/app/Interceptor.vue"))["default"] - AppLogo: (typeof import("./components/app/Logo.vue"))["default"] - AppOptions: (typeof import("./components/app/Options.vue"))["default"] - AppPaneLayout: (typeof import("./components/app/PaneLayout.vue"))["default"] - AppShare: (typeof import("./components/app/Share.vue"))["default"] - AppShortcuts: (typeof import("./components/app/Shortcuts.vue"))["default"] - AppShortcutsEntry: (typeof import("./components/app/ShortcutsEntry.vue"))["default"] - AppShortcutsPrompt: (typeof import("./components/app/ShortcutsPrompt.vue"))["default"] - AppSidenav: (typeof import("./components/app/Sidenav.vue"))["default"] - AppSpotlight: (typeof import("./components/app/spotlight/index.vue"))["default"] - AppSpotlightEntry: (typeof import("./components/app/spotlight/Entry.vue"))["default"] - AppSpotlightEntryGQLHistory: (typeof import("./components/app/spotlight/entry/GQLHistory.vue"))["default"] - AppSpotlightEntryGQLRequest: (typeof import("./components/app/spotlight/entry/GQLRequest.vue"))["default"] - AppSpotlightEntryIconSelected: (typeof import("./components/app/spotlight/entry/IconSelected.vue"))["default"] - AppSpotlightEntryRESTHistory: (typeof import("./components/app/spotlight/entry/RESTHistory.vue"))["default"] - AppSpotlightEntryRESTRequest: (typeof import("./components/app/spotlight/entry/RESTRequest.vue"))["default"] - AppSupport: (typeof import("./components/app/Support.vue"))["default"] - Collections: (typeof import("./components/collections/index.vue"))["default"] - CollectionsAdd: (typeof import("./components/collections/Add.vue"))["default"] - CollectionsAddFolder: (typeof import("./components/collections/AddFolder.vue"))["default"] - CollectionsAddRequest: (typeof import("./components/collections/AddRequest.vue"))["default"] - CollectionsCollection: (typeof import("./components/collections/Collection.vue"))["default"] - CollectionsEdit: (typeof import("./components/collections/Edit.vue"))["default"] - CollectionsEditFolder: (typeof import("./components/collections/EditFolder.vue"))["default"] - CollectionsEditRequest: (typeof import("./components/collections/EditRequest.vue"))["default"] - CollectionsGraphql: (typeof import("./components/collections/graphql/index.vue"))["default"] - CollectionsGraphqlAdd: (typeof import("./components/collections/graphql/Add.vue"))["default"] - CollectionsGraphqlAddFolder: (typeof import("./components/collections/graphql/AddFolder.vue"))["default"] - CollectionsGraphqlAddRequest: (typeof import("./components/collections/graphql/AddRequest.vue"))["default"] - CollectionsGraphqlCollection: (typeof import("./components/collections/graphql/Collection.vue"))["default"] - CollectionsGraphqlEdit: (typeof import("./components/collections/graphql/Edit.vue"))["default"] - CollectionsGraphqlEditFolder: (typeof import("./components/collections/graphql/EditFolder.vue"))["default"] - CollectionsGraphqlEditRequest: (typeof import("./components/collections/graphql/EditRequest.vue"))["default"] - CollectionsGraphqlFolder: (typeof import("./components/collections/graphql/Folder.vue"))["default"] - CollectionsGraphqlImportExport: (typeof import("./components/collections/graphql/ImportExport.vue"))["default"] - CollectionsGraphqlRequest: (typeof import("./components/collections/graphql/Request.vue"))["default"] - CollectionsImportExport: (typeof import("./components/collections/ImportExport.vue"))["default"] - CollectionsMyCollections: (typeof import("./components/collections/MyCollections.vue"))["default"] - CollectionsProperties: (typeof import("./components/collections/Properties.vue"))["default"] - CollectionsRequest: (typeof import("./components/collections/Request.vue"))["default"] - CollectionsSaveRequest: (typeof import("./components/collections/SaveRequest.vue"))["default"] - CollectionsTeamCollections: (typeof import("./components/collections/TeamCollections.vue"))["default"] - CookiesAllModal: (typeof import("./components/cookies/AllModal.vue"))["default"] - CookiesEditCookie: (typeof import("./components/cookies/EditCookie.vue"))["default"] - Embeds: (typeof import("./components/embeds/index.vue"))["default"] - Environments: (typeof import("./components/environments/index.vue"))["default"] - EnvironmentsAdd: (typeof import("./components/environments/Add.vue"))["default"] - EnvironmentsImportExport: (typeof import("./components/environments/ImportExport.vue"))["default"] - EnvironmentsMy: (typeof import("./components/environments/my/index.vue"))["default"] - EnvironmentsMyDetails: (typeof import("./components/environments/my/Details.vue"))["default"] - EnvironmentsMyEnvironment: (typeof import("./components/environments/my/Environment.vue"))["default"] - EnvironmentsSelector: (typeof import("./components/environments/Selector.vue"))["default"] - EnvironmentsTeams: (typeof import("./components/environments/teams/index.vue"))["default"] - EnvironmentsTeamsDetails: (typeof import("./components/environments/teams/Details.vue"))["default"] - EnvironmentsTeamsEnvironment: (typeof import("./components/environments/teams/Environment.vue"))["default"] - FirebaseLogin: (typeof import("./components/firebase/Login.vue"))["default"] - FirebaseLogout: (typeof import("./components/firebase/Logout.vue"))["default"] - GraphqlAuthorization: (typeof import("./components/graphql/Authorization.vue"))["default"] - GraphqlField: (typeof import("./components/graphql/Field.vue"))["default"] - GraphqlHeaders: (typeof import("./components/graphql/Headers.vue"))["default"] - GraphqlQuery: (typeof import("./components/graphql/Query.vue"))["default"] - GraphqlRequest: (typeof import("./components/graphql/Request.vue"))["default"] - GraphqlRequestOptions: (typeof import("./components/graphql/RequestOptions.vue"))["default"] - GraphqlRequestTab: (typeof import("./components/graphql/RequestTab.vue"))["default"] - GraphqlResponse: (typeof import("./components/graphql/Response.vue"))["default"] - GraphqlSidebar: (typeof import("./components/graphql/Sidebar.vue"))["default"] - GraphqlSubscriptionLog: (typeof import("./components/graphql/SubscriptionLog.vue"))["default"] - GraphqlTabHead: (typeof import("./components/graphql/TabHead.vue"))["default"] - GraphqlType: (typeof import("./components/graphql/Type.vue"))["default"] - GraphqlTypeLink: (typeof import("./components/graphql/TypeLink.vue"))["default"] - GraphqlVariable: (typeof import("./components/graphql/Variable.vue"))["default"] - History: (typeof import("./components/history/index.vue"))["default"] - HistoryGraphqlCard: (typeof import("./components/history/graphql/Card.vue"))["default"] - HistoryRestCard: (typeof import("./components/history/rest/Card.vue"))["default"] - HoppButtonPrimary: (typeof import("@hoppscotch/ui"))["HoppButtonPrimary"] - HoppButtonSecondary: (typeof import("@hoppscotch/ui"))["HoppButtonSecondary"] - HoppSmartAnchor: (typeof import("@hoppscotch/ui"))["HoppSmartAnchor"] - HoppSmartCheckbox: (typeof import("@hoppscotch/ui"))["HoppSmartCheckbox"] - HoppSmartConfirmModal: (typeof import("@hoppscotch/ui"))["HoppSmartConfirmModal"] + '(chore': fix broken runner for user collection) + '(feat': collection runner config in modal) + '(fix': run again function) + AccessTokens: typeof import('./components/accessTokens/index.vue')['default'] + AccessTokensGenerateModal: typeof import('./components/accessTokens/GenerateModal.vue')['default'] + AccessTokensList: typeof import('./components/accessTokens/List.vue')['default'] + AccessTokensOverview: typeof import('./components/accessTokens/Overview.vue')['default'] + AiexperimentsMergeView: typeof import('./components/aiexperiments/MergeView.vue')['default'] + AiexperimentsModifyBodyModal: typeof import('./components/aiexperiments/ModifyBodyModal.vue')['default'] + AiexperimentsModifyPreRequestModal: typeof import('./components/aiexperiments/ModifyPreRequestModal.vue')['default'] + AiexperimentsModifyTestScriptModal: typeof import('./components/aiexperiments/ModifyTestScriptModal.vue')['default'] + AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default'] + AppAnnouncement: (typeof import("./components/app/Announcement.vue"))["default"] + AppBanner: typeof import('./components/app/Banner.vue')['default'] + AppContextMenu: typeof import('./components/app/ContextMenu.vue')['default'] + AppDeveloperOptions: typeof import('./components/app/DeveloperOptions.vue')['default'] + AppFooter: typeof import('./components/app/Footer.vue')['default'] + AppGitHubStarButton: typeof import('./components/app/GitHubStarButton.vue')['default'] + AppHeader: typeof import('./components/app/Header.vue')['default'] + AppInspection: typeof import('./components/app/Inspection.vue')['default'] + AppInterceptor: typeof import('./components/app/Interceptor.vue')['default'] + AppLogo: typeof import('./components/app/Logo.vue')['default'] + AppOptions: typeof import('./components/app/Options.vue')['default'] + AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default'] + AppShare: typeof import('./components/app/Share.vue')['default'] + AppShortcuts: typeof import('./components/app/Shortcuts.vue')['default'] + AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default'] + AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default'] + AppSidenav: typeof import('./components/app/Sidenav.vue')['default'] + AppSpotlight: typeof import('./components/app/spotlight/index.vue')['default'] + AppSpotlightEntry: typeof import('./components/app/spotlight/Entry.vue')['default'] + AppSpotlightEntryGQLHistory: typeof import('./components/app/spotlight/entry/GQLHistory.vue')['default'] + AppSpotlightEntryGQLRequest: typeof import('./components/app/spotlight/entry/GQLRequest.vue')['default'] + AppSpotlightEntryIconSelected: typeof import('./components/app/spotlight/entry/IconSelected.vue')['default'] + AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default'] + AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default'] + AppSpotlightEntryRESTTeamRequestEntry: typeof import('./components/app/spotlight/entry/RESTTeamRequestEntry.vue')['default'] + AppSpotlightSearch: typeof import('./components/app/SpotlightSearch.vue')['default'] + AppSupport: typeof import('./components/app/Support.vue')['default'] + AppWhatsNewDialog: typeof import('./components/app/WhatsNewDialog.vue')['default'] + ButtonPrimary: (typeof import("./../../hoppscotch-ui/src/components/button/Primary.vue"))["default"] + ButtonSecondary: (typeof import("./../../hoppscotch-ui/src/components/button/Secondary.vue"))["default"] + Collections: typeof import('./components/collections/index.vue')['default'] + CollectionsAdd: typeof import('./components/collections/Add.vue')['default'] + CollectionsAddFolder: typeof import('./components/collections/AddFolder.vue')['default'] + CollectionsAddRequest: typeof import('./components/collections/AddRequest.vue')['default'] + CollectionsCollection: typeof import('./components/collections/Collection.vue')['default'] + CollectionsEdit: typeof import('./components/collections/Edit.vue')['default'] + CollectionsEditFolder: typeof import('./components/collections/EditFolder.vue')['default'] + CollectionsEditRequest: typeof import('./components/collections/EditRequest.vue')['default'] + CollectionsEditResponse: typeof import('./components/collections/EditResponse.vue')['default'] + CollectionsExampleResponse: typeof import('./components/collections/ExampleResponse.vue')['default'] + CollectionsGraphql: typeof import('./components/collections/graphql/index.vue')['default'] + CollectionsGraphqlAdd: typeof import('./components/collections/graphql/Add.vue')['default'] + CollectionsGraphqlAddFolder: typeof import('./components/collections/graphql/AddFolder.vue')['default'] + CollectionsGraphqlAddRequest: typeof import('./components/collections/graphql/AddRequest.vue')['default'] + CollectionsGraphqlCollection: typeof import('./components/collections/graphql/Collection.vue')['default'] + CollectionsGraphqlEdit: typeof import('./components/collections/graphql/Edit.vue')['default'] + CollectionsGraphqlEditFolder: typeof import('./components/collections/graphql/EditFolder.vue')['default'] + CollectionsGraphqlEditRequest: typeof import('./components/collections/graphql/EditRequest.vue')['default'] + CollectionsGraphqlFolder: typeof import('./components/collections/graphql/Folder.vue')['default'] + CollectionsGraphqlImportExport: typeof import('./components/collections/graphql/ImportExport.vue')['default'] + CollectionsGraphqlRequest: typeof import('./components/collections/graphql/Request.vue')['default'] + CollectionsImportExport: typeof import('./components/collections/ImportExport.vue')['default'] + CollectionsMyCollections: typeof import('./components/collections/MyCollections.vue')['default'] + CollectionsProperties: typeof import('./components/collections/Properties.vue')['default'] + CollectionsRequest: typeof import('./components/collections/Request.vue')['default'] + CollectionsRunner: (typeof import("./components/collections/Runner.vue"))["default"] + CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default'] + CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default'] + CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default'] + CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default'] + Embeds: typeof import('./components/embeds/index.vue')['default'] + EmbedsHeader: typeof import('./components/embeds/Header.vue')['default'] + EmbedsRequest: typeof import('./components/embeds/Request.vue')['default'] + Environments: typeof import('./components/environments/index.vue')['default'] + EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default'] + EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default'] + EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default'] + EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default'] + EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.vue')['default'] + EnvironmentsProperties: typeof import('./components/environments/Properties.vue')['default'] + EnvironmentsSelector: typeof import('./components/environments/Selector.vue')['default'] + EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default'] + EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default'] + EnvironmentsTeamsEnvironment: typeof import('./components/environments/teams/Environment.vue')['default'] + FirebaseLogin: typeof import('./components/firebase/Login.vue')['default'] + FirebaseLogout: typeof import('./components/firebase/Logout.vue')['default'] + GraphqlAuthorization: typeof import('./components/graphql/Authorization.vue')['default'] + GraphqlField: typeof import('./components/graphql/Field.vue')['default'] + GraphqlHeaders: typeof import('./components/graphql/Headers.vue')['default'] + GraphqlQuery: typeof import('./components/graphql/Query.vue')['default'] + GraphqlRequest: typeof import('./components/graphql/Request.vue')['default'] + GraphqlRequestOptions: typeof import('./components/graphql/RequestOptions.vue')['default'] + GraphqlRequestTab: typeof import('./components/graphql/RequestTab.vue')['default'] + GraphqlResponse: typeof import('./components/graphql/Response.vue')['default'] + GraphqlSidebar: typeof import('./components/graphql/Sidebar.vue')['default'] + GraphqlSubscriptionLog: typeof import('./components/graphql/SubscriptionLog.vue')['default'] + GraphqlTabHead: typeof import('./components/graphql/TabHead.vue')['default'] + GraphqlType: typeof import('./components/graphql/Type.vue')['default'] + GraphqlTypeLink: typeof import('./components/graphql/TypeLink.vue')['default'] + GraphqlVariable: typeof import('./components/graphql/Variable.vue')['default'] + History: typeof import('./components/history/index.vue')['default'] + HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default'] + HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default'] + HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary'] + HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'] + HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor'] + HoppSmartAutoComplete: (typeof import("@hoppscotch/ui"))["HoppSmartAutoComplete"] + HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox'] + HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'] HoppSmartExpand: (typeof import("@hoppscotch/ui"))["HoppSmartExpand"] - HoppSmartFileChip: (typeof import("@hoppscotch/ui"))["HoppSmartFileChip"] - HoppSmartInput: (typeof import("@hoppscotch/ui"))["HoppSmartInput"] - HoppSmartIntersection: (typeof import("@hoppscotch/ui"))["HoppSmartIntersection"] - HoppSmartItem: (typeof import("@hoppscotch/ui"))["HoppSmartItem"] - HoppSmartLink: (typeof import("@hoppscotch/ui"))["HoppSmartLink"] - HoppSmartModal: (typeof import("@hoppscotch/ui"))["HoppSmartModal"] - HoppSmartPicture: (typeof import("@hoppscotch/ui"))["HoppSmartPicture"] - HoppSmartPlaceholder: (typeof import("@hoppscotch/ui"))["HoppSmartPlaceholder"] - HoppSmartProgressRing: (typeof import("@hoppscotch/ui"))["HoppSmartProgressRing"] - HoppSmartRadio: (typeof import("@hoppscotch/ui"))["HoppSmartRadio"] - HoppSmartRadioGroup: (typeof import("@hoppscotch/ui"))["HoppSmartRadioGroup"] - HoppSmartSelectWrapper: (typeof import("@hoppscotch/ui"))["HoppSmartSelectWrapper"] - HoppSmartSlideOver: (typeof import("@hoppscotch/ui"))["HoppSmartSlideOver"] - HoppSmartSpinner: (typeof import("@hoppscotch/ui"))["HoppSmartSpinner"] - HoppSmartTab: (typeof import("@hoppscotch/ui"))["HoppSmartTab"] - HoppSmartTabs: (typeof import("@hoppscotch/ui"))["HoppSmartTabs"] - HoppSmartToggle: (typeof import("@hoppscotch/ui"))["HoppSmartToggle"] - HoppSmartTree: (typeof import("@hoppscotch/ui"))["HoppSmartTree"] - HoppSmartWindow: (typeof import("@hoppscotch/ui"))["HoppSmartWindow"] - HoppSmartWindows: (typeof import("@hoppscotch/ui"))["HoppSmartWindows"] - HttpAuthorization: (typeof import("./components/http/Authorization.vue"))["default"] - HttpAuthorizationApiKey: (typeof import("./components/http/authorization/ApiKey.vue"))["default"] - HttpAuthorizationBasic: (typeof import("./components/http/authorization/Basic.vue"))["default"] - HttpBody: (typeof import("./components/http/Body.vue"))["default"] - HttpBodyParameters: (typeof import("./components/http/BodyParameters.vue"))["default"] - HttpCodegenModal: (typeof import("./components/http/CodegenModal.vue"))["default"] - HttpHeaders: (typeof import("./components/http/Headers.vue"))["default"] - HttpImportCurl: (typeof import("./components/http/ImportCurl.vue"))["default"] + HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip'] + HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'] + HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection'] + HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'] + HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink'] + HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal'] + HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture'] + HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder'] + HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing'] + HoppSmartRadio: typeof import('@hoppscotch/ui')['HoppSmartRadio'] + HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup'] + HoppSmartSelectWrapper: typeof import('@hoppscotch/ui')['HoppSmartSelectWrapper'] + HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver'] + HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'] + HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab'] + HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs'] + HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle'] + HoppSmartTree: typeof import('@hoppscotch/ui')['HoppSmartTree'] + HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow'] + HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows'] + HoppTestEnv: (typeof import("@hoppscotch/ui"))["HoppTestEnv"] + HoppTestRunnerModal: (typeof import("@hoppscotch/ui"))["HoppTestRunnerModal"] + HttpAuthorization: typeof import('./components/http/Authorization.vue')['default'] + HttpAuthorizationAkamaiEG: typeof import('./components/http/authorization/AkamaiEG.vue')['default'] + HttpAuthorizationApiKey: typeof import('./components/http/authorization/ApiKey.vue')['default'] + HttpAuthorizationASAP: typeof import('./components/http/authorization/ASAP.vue')['default'] + HttpAuthorizationAWSSign: typeof import('./components/http/authorization/AWSSign.vue')['default'] + HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default'] + HttpAuthorizationDigest: typeof import('./components/http/authorization/Digest.vue')['default'] + HttpAuthorizationHAWK: typeof import('./components/http/authorization/HAWK.vue')['default'] + HttpAuthorizationNTLM: typeof import('./components/http/authorization/NTLM.vue')['default'] + HttpAuthorizationOAuth2: typeof import('./components/http/authorization/OAuth2.vue')['default'] + HttpBody: typeof import('./components/http/Body.vue')['default'] + HttpBodyBinary: typeof import('./components/http/BodyBinary.vue')['default'] + HttpBodyParameters: typeof import('./components/http/BodyParameters.vue')['default'] + HttpCodegen: typeof import('./components/http/Codegen.vue')['default'] + HttpCodegenModal: typeof import('./components/http/CodegenModal.vue')['default'] + HttpCollectionRunner: (typeof import("./components/http/CollectionRunner.vue"))["default"] + HttpExampleLenseBodyRenderer: typeof import('./components/http/example/LenseBodyRenderer.vue')['default'] + HttpExampleResponse: typeof import('./components/http/example/Response.vue')['default'] + HttpExampleResponseMeta: typeof import('./components/http/example/ResponseMeta.vue')['default'] + HttpExampleResponseRequest: typeof import('./components/http/example/ResponseRequest.vue')['default'] + HttpExampleResponseTab: typeof import('./components/http/example/ResponseTab.vue')['default'] + HttpHeaders: typeof import('./components/http/Headers.vue')['default'] + HttpImportCurl: typeof import('./components/http/ImportCurl.vue')['default'] + HttpKeyValue: typeof import('./components/http/KeyValue.vue')['default'] HttpOAuth2Authorization: (typeof import("./components/http/OAuth2Authorization.vue"))["default"] - HttpParameters: (typeof import("./components/http/Parameters.vue"))["default"] - HttpPreRequestScript: (typeof import("./components/http/PreRequestScript.vue"))["default"] - HttpRawBody: (typeof import("./components/http/RawBody.vue"))["default"] - HttpReqChangeConfirmModal: (typeof import("./components/http/ReqChangeConfirmModal.vue"))["default"] - HttpRequest: (typeof import("./components/http/Request.vue"))["default"] - HttpRequestOptions: (typeof import("./components/http/RequestOptions.vue"))["default"] - HttpRequestTab: (typeof import("./components/http/RequestTab.vue"))["default"] - HttpResponse: (typeof import("./components/http/Response.vue"))["default"] - HttpResponseMeta: (typeof import("./components/http/ResponseMeta.vue"))["default"] - HttpSidebar: (typeof import("./components/http/Sidebar.vue"))["default"] - HttpTabHead: (typeof import("./components/http/TabHead.vue"))["default"] - HttpTestResult: (typeof import("./components/http/TestResult.vue"))["default"] - HttpTestResultEntry: (typeof import("./components/http/TestResultEntry.vue"))["default"] - HttpTestResultEnv: (typeof import("./components/http/TestResultEnv.vue"))["default"] - HttpTestResultReport: (typeof import("./components/http/TestResultReport.vue"))["default"] - HttpTests: (typeof import("./components/http/Tests.vue"))["default"] - HttpURLEncodedParams: (typeof import("./components/http/URLEncodedParams.vue"))["default"] - IconLucideActivity: (typeof import("~icons/lucide/activity"))["default"] - IconLucideAlertTriangle: (typeof import("~icons/lucide/alert-triangle"))["default"] - IconLucideArrowLeft: (typeof import("~icons/lucide/arrow-left"))["default"] - IconLucideArrowUpRight: (typeof import("~icons/lucide/arrow-up-right"))["default"] + HttpParameters: typeof import('./components/http/Parameters.vue')['default'] + HttpPreRequestScript: typeof import('./components/http/PreRequestScript.vue')['default'] + HttpRawBody: typeof import('./components/http/RawBody.vue')['default'] + HttpReqChangeConfirmModal: typeof import('./components/http/ReqChangeConfirmModal.vue')['default'] + HttpRequest: typeof import('./components/http/Request.vue')['default'] + HttpRequestOptions: typeof import('./components/http/RequestOptions.vue')['default'] + HttpRequestTab: typeof import('./components/http/RequestTab.vue')['default'] + HttpRequestVariables: typeof import('./components/http/RequestVariables.vue')['default'] + HttpResponse: typeof import('./components/http/Response.vue')['default'] + HttpResponseInterface: typeof import('./components/http/ResponseInterface.vue')['default'] + HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default'] + HttpRunner: (typeof import("./components/http/Runner.vue"))["default"] + HttpSaveResponseName: typeof import('./components/http/SaveResponseName.vue')['default'] + HttpSidebar: typeof import('./components/http/Sidebar.vue')['default'] + HttpTabHead: typeof import('./components/http/TabHead.vue')['default'] + HttpTestEnv: typeof import('./components/http/test/Env.vue')['default'] + HttpTestFolder: typeof import('./components/http/test/Folder.vue')['default'] + HttpTestRequest: typeof import('./components/http/test/Request.vue')['default'] + HttpTestResponse: typeof import('./components/http/test/Response.vue')['default'] + HttpTestResult: typeof import('./components/http/TestResult.vue')['default'] + HttpTestResultEntry: typeof import('./components/http/TestResultEntry.vue')['default'] + HttpTestResultEnv: typeof import('./components/http/TestResultEnv.vue')['default'] + HttpTestResultFolder: typeof import('./components/http/test/ResultFolder.vue')['default'] + HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default'] + HttpTestResultRequest: typeof import('./components/http/test/ResultRequest.vue')['default'] + HttpTestRunner: typeof import('./components/http/test/Runner.vue')['default'] + HttpTestRunnerConfig: typeof import('./components/http/test/RunnerConfig.vue')['default'] + HttpTestRunnerMeta: typeof import('./components/http/test/RunnerMeta.vue')['default'] + HttpTestRunnerModal: typeof import('./components/http/test/RunnerModal.vue')['default'] + HttpTestRunnerResult: typeof import('./components/http/test/RunnerResult.vue')['default'] + HttpTests: typeof import('./components/http/Tests.vue')['default'] + HttpTestSelector: (typeof import("./components/http/test/Selector.vue"))["default"] + HttpTestSelectRequest: (typeof import("./components/http/test/SelectRequest.vue"))["default"] + HttpTestTestResult: typeof import('./components/http/test/TestResult.vue')['default'] + HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default'] + IconLucideActivity: typeof import('~icons/lucide/activity')['default'] + IconLucideAlertCircle: (typeof import("~icons/lucide/alert-circle"))["default"] + IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default'] + IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] + IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default'] IconLucideBrush: (typeof import("~icons/lucide/brush"))["default"] - IconLucideCheckCircle: (typeof import("~icons/lucide/check-circle"))["default"] - IconLucideChevronRight: (typeof import("~icons/lucide/chevron-right"))["default"] - IconLucideGlobe: (typeof import("~icons/lucide/globe"))["default"] - IconLucideHelpCircle: (typeof import("~icons/lucide/help-circle"))["default"] - IconLucideInbox: (typeof import("~icons/lucide/inbox"))["default"] - IconLucideInfo: (typeof import("~icons/lucide/info"))["default"] - IconLucideLayers: (typeof import("~icons/lucide/layers"))["default"] - IconLucideListEnd: (typeof import("~icons/lucide/list-end"))["default"] - IconLucideMinus: (typeof import("~icons/lucide/minus"))["default"] + IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] + IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] + IconLucideGlobe: typeof import('~icons/lucide/globe')['default'] + IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default'] + IconLucideInbox: typeof import('~icons/lucide/inbox')['default'] + IconLucideInfo: typeof import('~icons/lucide/info')['default'] + IconLucideLayers: typeof import('~icons/lucide/layers')['default'] + IconLucideListEnd: typeof import('~icons/lucide/list-end')['default'] + IconLucideMinus: typeof import('~icons/lucide/minus')['default'] + IconLucidePlay: (typeof import("~icons/lucide/play"))["default"] + IconLucidePlaySquare: (typeof import("~icons/lucide/play-square"))["default"] IconLucideRss: (typeof import("~icons/lucide/rss"))["default"] - IconLucideSearch: (typeof import("~icons/lucide/search"))["default"] - IconLucideUsers: (typeof import("~icons/lucide/users"))["default"] - IconLucideX: (typeof import("~icons/lucide/x"))["default"] - ImportExportBase: (typeof import("./components/importExport/Base.vue"))["default"] - ImportExportImportExportList: (typeof import("./components/importExport/ImportExportList.vue"))["default"] - ImportExportImportExportSourcesList: (typeof import("./components/importExport/ImportExportSourcesList.vue"))["default"] - ImportExportImportExportStepsFileImport: (typeof import("./components/importExport/ImportExportSteps/FileImport.vue"))["default"] - ImportExportImportExportStepsMyCollectionImport: (typeof import("./components/importExport/ImportExportSteps/MyCollectionImport.vue"))["default"] - ImportExportImportExportStepsUrlImport: (typeof import("./components/importExport/ImportExportSteps/UrlImport.vue"))["default"] - InterceptorsErrorPlaceholder: (typeof import("./components/interceptors/ErrorPlaceholder.vue"))["default"] - InterceptorsExtensionSubtitle: (typeof import("./components/interceptors/ExtensionSubtitle.vue"))["default"] - LensesHeadersRenderer: (typeof import("./components/lenses/HeadersRenderer.vue"))["default"] - LensesHeadersRendererEntry: (typeof import("./components/lenses/HeadersRendererEntry.vue"))["default"] - LensesRenderersAudioLensRenderer: (typeof import("./components/lenses/renderers/AudioLensRenderer.vue"))["default"] - LensesRenderersHTMLLensRenderer: (typeof import("./components/lenses/renderers/HTMLLensRenderer.vue"))["default"] - LensesRenderersImageLensRenderer: (typeof import("./components/lenses/renderers/ImageLensRenderer.vue"))["default"] - LensesRenderersJSONLensRenderer: (typeof import("./components/lenses/renderers/JSONLensRenderer.vue"))["default"] - LensesRenderersPDFLensRenderer: (typeof import("./components/lenses/renderers/PDFLensRenderer.vue"))["default"] - LensesRenderersRawLensRenderer: (typeof import("./components/lenses/renderers/RawLensRenderer.vue"))["default"] - LensesRenderersVideoLensRenderer: (typeof import("./components/lenses/renderers/VideoLensRenderer.vue"))["default"] - LensesRenderersXMLLensRenderer: (typeof import("./components/lenses/renderers/XMLLensRenderer.vue"))["default"] - LensesResponseBodyRenderer: (typeof import("./components/lenses/ResponseBodyRenderer.vue"))["default"] - ProfileUserDelete: (typeof import("./components/profile/UserDelete.vue"))["default"] - RealtimeCommunication: (typeof import("./components/realtime/Communication.vue"))["default"] - RealtimeConnectionConfig: (typeof import("./components/realtime/ConnectionConfig.vue"))["default"] - RealtimeLog: (typeof import("./components/realtime/Log.vue"))["default"] - RealtimeLogEntry: (typeof import("./components/realtime/LogEntry.vue"))["default"] - RealtimeSubscription: (typeof import("./components/realtime/Subscription.vue"))["default"] - SettingsExtension: (typeof import("./components/settings/Extension.vue"))["default"] - SettingsProxy: (typeof import("./components/settings/Proxy.vue"))["default"] - Share: (typeof import("./components/share/index.vue"))["default"] - ShareCreateModal: (typeof import("./components/share/CreateModal.vue"))["default"] - ShareCustomizeModal: (typeof import("./components/share/CustomizeModal.vue"))["default"] - ShareModal: (typeof import("./components/share/Modal.vue"))["default"] - ShareRequest: (typeof import("./components/share/Request.vue"))["default"] - ShareTemplatesButton: (typeof import("./components/share/templates/Button.vue"))["default"] - ShareTemplatesEmbeds: (typeof import("./components/share/templates/Embeds.vue"))["default"] - ShareTemplatesLink: (typeof import("./components/share/templates/Link.vue"))["default"] - SmartAccentModePicker: (typeof import("./components/smart/AccentModePicker.vue"))["default"] - SmartChangeLanguage: (typeof import("./components/smart/ChangeLanguage.vue"))["default"] - SmartColorModePicker: (typeof import("./components/smart/ColorModePicker.vue"))["default"] - SmartEnvInput: (typeof import("./components/smart/EnvInput.vue"))["default"] - TabPrimary: (typeof import("./components/tab/Primary.vue"))["default"] - TabSecondary: (typeof import("./components/tab/Secondary.vue"))["default"] - Teams: (typeof import("./components/teams/index.vue"))["default"] - TeamsAdd: (typeof import("./components/teams/Add.vue"))["default"] - TeamsEdit: (typeof import("./components/teams/Edit.vue"))["default"] - TeamsInvite: (typeof import("./components/teams/Invite.vue"))["default"] - TeamsMemberStack: (typeof import("./components/teams/MemberStack.vue"))["default"] - TeamsModal: (typeof import("./components/teams/Modal.vue"))["default"] - TeamsTeam: (typeof import("./components/teams/Team.vue"))["default"] - Tippy: (typeof import("vue-tippy"))["Tippy"] - WorkspaceCurrent: (typeof import("./components/workspace/Current.vue"))["default"] - WorkspaceSelector: (typeof import("./components/workspace/Selector.vue"))["default"] + IconLucideSearch: typeof import('~icons/lucide/search')['default'] + IconLucideUsers: typeof import('~icons/lucide/users')['default'] + IconLucideVerified: typeof import('~icons/lucide/verified')['default'] + IconLucideX: typeof import('~icons/lucide/x')['default'] + ImportExportBase: typeof import('./components/importExport/Base.vue')['default'] + ImportExportImportExportList: typeof import('./components/importExport/ImportExportList.vue')['default'] + ImportExportImportExportSourcesList: typeof import('./components/importExport/ImportExportSourcesList.vue')['default'] + ImportExportImportExportStepsAllCollectionImport: typeof import('./components/importExport/ImportExportSteps/AllCollectionImport.vue')['default'] + ImportExportImportExportStepsFileImport: typeof import('./components/importExport/ImportExportSteps/FileImport.vue')['default'] + ImportExportImportExportStepsImportSummary: typeof import('./components/importExport/ImportExportSteps/ImportSummary.vue')['default'] + ImportExportImportExportStepsMyCollectionImport: typeof import('./components/importExport/ImportExportSteps/MyCollectionImport.vue')['default'] + ImportExportImportExportStepsUrlImport: typeof import('./components/importExport/ImportExportSteps/UrlImport.vue')['default'] + InterceptorsAgentModalNativeCACertificates: typeof import('./components/interceptors/agent/ModalNativeCACertificates.vue')['default'] + InterceptorsAgentModalNativeClientCertificates: typeof import('./components/interceptors/agent/ModalNativeClientCertificates.vue')['default'] + InterceptorsAgentModalNativeClientCertsAdd: typeof import('./components/interceptors/agent/ModalNativeClientCertsAdd.vue')['default'] + InterceptorsAgentRegistrationModal: typeof import('./components/interceptors/agent/RegistrationModal.vue')['default'] + InterceptorsAgentRootExt: typeof import('./components/interceptors/agent/RootExt.vue')['default'] + InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default'] + InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default'] + LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default'] + LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default'] + LensesRenderersAudioLensRenderer: typeof import('./components/lenses/renderers/AudioLensRenderer.vue')['default'] + LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default'] + LensesRenderersImageLensRenderer: typeof import('./components/lenses/renderers/ImageLensRenderer.vue')['default'] + LensesRenderersJSONLensRenderer: typeof import('./components/lenses/renderers/JSONLensRenderer.vue')['default'] + LensesRenderersPDFLensRenderer: typeof import('./components/lenses/renderers/PDFLensRenderer.vue')['default'] + LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default'] + LensesRenderersVideoLensRenderer: typeof import('./components/lenses/renderers/VideoLensRenderer.vue')['default'] + LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default'] + LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default'] + ProfileShortcode: (typeof import("./components/profile/Shortcode.vue"))["default"] + ProfileShortcodes: (typeof import("./components/profile/Shortcodes.vue"))["default"] + ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default'] + RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default'] + RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default'] + RealtimeLog: typeof import('./components/realtime/Log.vue')['default'] + RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default'] + RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default'] + SettingsAgent: typeof import('./components/settings/Agent.vue')['default'] + SettingsExtension: typeof import('./components/settings/Extension.vue')['default'] + SettingsProxy: typeof import('./components/settings/Proxy.vue')['default'] + Share: typeof import('./components/share/index.vue')['default'] + ShareCreateModal: typeof import('./components/share/CreateModal.vue')['default'] + ShareCustomizeModal: typeof import('./components/share/CustomizeModal.vue')['default'] + ShareModal: typeof import('./components/share/Modal.vue')['default'] + ShareRequest: typeof import('./components/share/Request.vue')['default'] + ShareRequestModal: (typeof import("./components/share/RequestModal.vue"))["default"] + ShareShareRequestModal: (typeof import("./components/share/ShareRequestModal.vue"))["default"] + ShareTemplatesButton: typeof import('./components/share/templates/Button.vue')['default'] + ShareTemplatesEmbeds: typeof import('./components/share/templates/Embeds.vue')['default'] + ShareTemplatesLink: typeof import('./components/share/templates/Link.vue')['default'] + SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default'] + SmartAnchor: (typeof import("./../../hoppscotch-ui/src/components/smart/Anchor.vue"))["default"] + SmartAutoComplete: (typeof import("./../../hoppscotch-ui/src/components/smart/AutoComplete.vue"))["default"] + SmartChangeLanguage: typeof import('./components/smart/ChangeLanguage.vue')['default'] + SmartCheckbox: (typeof import("./../../hoppscotch-ui/src/components/smart/Checkbox.vue"))["default"] + SmartColorModePicker: typeof import('./components/smart/ColorModePicker.vue')['default'] + SmartConfirmModal: (typeof import("./../../hoppscotch-ui/src/components/smart/ConfirmModal.vue"))["default"] + SmartEncodingPicker: typeof import('./components/smart/EncodingPicker.vue')['default'] + SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default'] + SmartExpand: (typeof import("./../../hoppscotch-ui/src/components/smart/Expand.vue"))["default"] + SmartFileChip: (typeof import("./../../hoppscotch-ui/src/components/smart/FileChip.vue"))["default"] + SmartInput: (typeof import("./../../hoppscotch-ui/src/components/smart/Input.vue"))["default"] + SmartIntersection: (typeof import("./../../hoppscotch-ui/src/components/smart/Intersection.vue"))["default"] + SmartItem: (typeof import("./../../hoppscotch-ui/src/components/smart/Item.vue"))["default"] + SmartLink: (typeof import("./../../hoppscotch-ui/src/components/smart/Link.vue"))["default"] + SmartModal: (typeof import("./../../hoppscotch-ui/src/components/smart/Modal.vue"))["default"] + SmartPicture: (typeof import("./../../hoppscotch-ui/src/components/smart/Picture.vue"))["default"] + SmartPlaceholder: (typeof import("./../../hoppscotch-ui/src/components/smart/Placeholder.vue"))["default"] + SmartProgressRing: (typeof import("./../../hoppscotch-ui/src/components/smart/ProgressRing.vue"))["default"] + SmartRadio: (typeof import("./../../hoppscotch-ui/src/components/smart/Radio.vue"))["default"] + SmartRadioGroup: (typeof import("./../../hoppscotch-ui/src/components/smart/RadioGroup.vue"))["default"] + SmartSelectWrapper: (typeof import("./../../hoppscotch-ui/src/components/smart/SelectWrapper.vue"))["default"] + SmartSlideOver: (typeof import("./../../hoppscotch-ui/src/components/smart/SlideOver.vue"))["default"] + SmartSpinner: (typeof import("./../../hoppscotch-ui/src/components/smart/Spinner.vue"))["default"] + SmartTab: (typeof import("./../../hoppscotch-ui/src/components/smart/Tab.vue"))["default"] + SmartTable: (typeof import("./../../hoppscotch-ui/src/components/smart/Table.vue"))["default"] + SmartTabs: (typeof import("./../../hoppscotch-ui/src/components/smart/Tabs.vue"))["default"] + SmartToggle: (typeof import("./../../hoppscotch-ui/src/components/smart/Toggle.vue"))["default"] + SmartTree: (typeof import("./../../hoppscotch-ui/src/components/smart/Tree.vue"))["default"] + SmartTreeBranch: (typeof import("./../../hoppscotch-ui/src/components/smart/TreeBranch.vue"))["default"] + SmartWindow: (typeof import("./../../hoppscotch-ui/src/components/smart/Window.vue"))["default"] + SmartWindows: (typeof import("./../../hoppscotch-ui/src/components/smart/Windows.vue"))["default"] + TabPrimary: typeof import('./components/tab/Primary.vue')['default'] + TabSecondary: typeof import('./components/tab/Secondary.vue')['default'] + Teams: typeof import('./components/teams/index.vue')['default'] + TeamsAdd: typeof import('./components/teams/Add.vue')['default'] + TeamsEdit: typeof import('./components/teams/Edit.vue')['default'] + TeamsInvite: typeof import('./components/teams/Invite.vue')['default'] + TeamsMemberStack: typeof import('./components/teams/MemberStack.vue')['default'] + TeamsModal: typeof import('./components/teams/Modal.vue')['default'] + TeamsTeam: typeof import('./components/teams/Team.vue')['default'] + Tippy: typeof import('vue-tippy')['Tippy'] + WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default'] + WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default'] } } diff --git a/packages/hoppscotch-common/src/components/accessTokens/GenerateModal.vue b/packages/hoppscotch-common/src/components/accessTokens/GenerateModal.vue new file mode 100644 index 0000000000..b368d75660 --- /dev/null +++ b/packages/hoppscotch-common/src/components/accessTokens/GenerateModal.vue @@ -0,0 +1,221 @@ + + + diff --git a/packages/hoppscotch-common/src/components/accessTokens/List.vue b/packages/hoppscotch-common/src/components/accessTokens/List.vue new file mode 100644 index 0000000000..5fe799029d --- /dev/null +++ b/packages/hoppscotch-common/src/components/accessTokens/List.vue @@ -0,0 +1,128 @@ + + + diff --git a/packages/hoppscotch-common/src/components/accessTokens/Overview.vue b/packages/hoppscotch-common/src/components/accessTokens/Overview.vue new file mode 100644 index 0000000000..0b60e5dff3 --- /dev/null +++ b/packages/hoppscotch-common/src/components/accessTokens/Overview.vue @@ -0,0 +1,30 @@ + + + diff --git a/packages/hoppscotch-common/src/components/accessTokens/index.vue b/packages/hoppscotch-common/src/components/accessTokens/index.vue new file mode 100644 index 0000000000..ef33b52683 --- /dev/null +++ b/packages/hoppscotch-common/src/components/accessTokens/index.vue @@ -0,0 +1,208 @@ + + + diff --git a/packages/hoppscotch-common/src/components/aiexperiments/MergeView.vue b/packages/hoppscotch-common/src/components/aiexperiments/MergeView.vue new file mode 100644 index 0000000000..05efd5d26b --- /dev/null +++ b/packages/hoppscotch-common/src/components/aiexperiments/MergeView.vue @@ -0,0 +1,82 @@ + + + diff --git a/packages/hoppscotch-common/src/components/aiexperiments/ModifyBodyModal.vue b/packages/hoppscotch-common/src/components/aiexperiments/ModifyBodyModal.vue new file mode 100644 index 0000000000..910ba57d90 --- /dev/null +++ b/packages/hoppscotch-common/src/components/aiexperiments/ModifyBodyModal.vue @@ -0,0 +1,161 @@ + + + diff --git a/packages/hoppscotch-common/src/components/aiexperiments/ModifyPreRequestModal.vue b/packages/hoppscotch-common/src/components/aiexperiments/ModifyPreRequestModal.vue new file mode 100644 index 0000000000..8025633644 --- /dev/null +++ b/packages/hoppscotch-common/src/components/aiexperiments/ModifyPreRequestModal.vue @@ -0,0 +1,159 @@ + + + diff --git a/packages/hoppscotch-common/src/components/aiexperiments/ModifyTestScriptModal.vue b/packages/hoppscotch-common/src/components/aiexperiments/ModifyTestScriptModal.vue new file mode 100644 index 0000000000..50b0d52427 --- /dev/null +++ b/packages/hoppscotch-common/src/components/aiexperiments/ModifyTestScriptModal.vue @@ -0,0 +1,159 @@ + + + diff --git a/packages/hoppscotch-common/src/components/app/ActionHandler.vue b/packages/hoppscotch-common/src/components/app/ActionHandler.vue index 89aaa64a02..9253b0edcf 100644 --- a/packages/hoppscotch-common/src/components/app/ActionHandler.vue +++ b/packages/hoppscotch-common/src/components/app/ActionHandler.vue @@ -2,6 +2,11 @@ + diff --git a/packages/hoppscotch-common/src/components/app/Header.vue b/packages/hoppscotch-common/src/components/app/Header.vue index 55557f960d..07ae5e73eb 100644 --- a/packages/hoppscotch-common/src/components/app/Header.vue +++ b/packages/hoppscotch-common/src/components/app/Header.vue @@ -21,19 +21,7 @@
- +
@@ -55,12 +43,19 @@ @click="invokeAction('modals.support.toggle')" />
-
+
-
- + +
@@ -96,6 +95,7 @@ /> + !!platform.platformFeatureFlags.workspaceSwitcherLogin?.value +) + /** * Once the PWA code is initialized, this holds a method * that can be called to show the user the installation @@ -287,31 +293,38 @@ const mdAndLarger = breakpoints.greater("md") const banner = useService(BannerService) const bannerContent = computed(() => banner.content.value?.content) -let bannerID: number | null = null +let offlineBannerID: number | null = null const offlineBanner: BannerContent = { type: "warning", text: (t) => t("helpers.offline"), alternateText: (t) => t("helpers.offline_short"), - score: BANNER_PRIORITY_HIGH, + score: BANNER_PRIORITY_LOW, dismissible: true, } +// Show the offline banner if the app is offline const network = reactive(useNetwork()) const isOnline = computed(() => network.isOnline) -// Show the offline banner if the user is offline watch(isOnline, () => { if (!isOnline.value) { - bannerID = banner.showBanner(offlineBanner) + offlineBannerID = banner.showBanner(offlineBanner) return } - if (banner.content && bannerID) { - banner.removeBanner(bannerID) + if (banner.content && offlineBannerID) { + banner.removeBanner(offlineBannerID) } }) -const dismissOfflineBanner = () => banner.removeBanner(bannerID!) +const dismissBanner = () => { + if (banner.content.value) { + banner.removeBanner(banner.content.value.id) + } else if (offlineBannerID) { + banner.removeBanner(offlineBannerID) + offlineBannerID = null + } +} const currentUser = useReadonlyStream( platform.auth.getProbableUserStream(), @@ -393,6 +406,8 @@ const inviteTeam = (team: { name: string }, teamID: string) => { // Show the workspace selected team invite modal if the user is an owner of the team else show the default invite modal const handleInvite = () => { + if (!currentUser.value) return invokeAction("modals.login.toggle") + if ( workspace.value.type === "team" && workspace.value.teamID && diff --git a/packages/hoppscotch-common/src/components/app/Inspection.vue b/packages/hoppscotch-common/src/components/app/Inspection.vue index e44bd75c8c..9338f66c5f 100644 --- a/packages/hoppscotch-common/src/components/app/Inspection.vue +++ b/packages/hoppscotch-common/src/components/app/Inspection.vue @@ -42,6 +42,7 @@ > {{ inspector.text.text }}
-
+

{{ t("settings.interceptor") }}

@@ -19,6 +19,9 @@ :value="interceptor.interceptorID" :label="unref(interceptor.name(t))" :selected="interceptorSelection === interceptor.interceptorID" + :class="{ + '!px-0 hover:bg-transparent': !isTooltipComponent, + }" @change="interceptorSelection = interceptor.interceptorID" /> @@ -39,6 +42,15 @@ import { InterceptorService } from "~/services/interceptor.service" const t = useI18n() +withDefaults( + defineProps<{ + isTooltipComponent?: boolean + }>(), + { + isTooltipComponent: true, + } +) + const interceptorService = useService(InterceptorService) const interceptorSelection = diff --git a/packages/hoppscotch-common/src/components/app/PaneLayout.vue b/packages/hoppscotch-common/src/components/app/PaneLayout.vue index 2b038c03e0..59c0e92461 100644 --- a/packages/hoppscotch-common/src/components/app/PaneLayout.vue +++ b/packages/hoppscotch-common/src/components/app/PaneLayout.vue @@ -16,13 +16,13 @@ > @@ -78,6 +78,14 @@ const props = defineProps({ type: String, default: null, }, + isEmbed: { + type: Boolean, + defaul: false, + }, + forceColumnLayout: { + type: Boolean, + default: false, + }, }) type PaneEvent = { diff --git a/packages/hoppscotch-common/src/components/app/SpotlightSearch.vue b/packages/hoppscotch-common/src/components/app/SpotlightSearch.vue new file mode 100644 index 0000000000..6704390bb4 --- /dev/null +++ b/packages/hoppscotch-common/src/components/app/SpotlightSearch.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/packages/hoppscotch-common/src/components/app/WhatsNewDialog.vue b/packages/hoppscotch-common/src/components/app/WhatsNewDialog.vue new file mode 100644 index 0000000000..9df856ea69 --- /dev/null +++ b/packages/hoppscotch-common/src/components/app/WhatsNewDialog.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/packages/hoppscotch-common/src/components/app/spotlight/Entry.vue b/packages/hoppscotch-common/src/components/app/spotlight/Entry.vue index a5b07eeb88..aecc5b1b51 100644 --- a/packages/hoppscotch-common/src/components/app/spotlight/Entry.vue +++ b/packages/hoppscotch-common/src/components/app/spotlight/Entry.vue @@ -80,11 +80,10 @@ const props = defineProps<{ active: boolean }>() -const formattedShortcutKeys = computed( - () => - props.entry.meta?.keyboardShortcut?.map( - (key) => SPECIAL_KEY_CHARS[key] ?? capitalize(key) - ) +const formattedShortcutKeys = computed(() => + props.entry.meta?.keyboardShortcut?.map( + (key) => SPECIAL_KEY_CHARS[key] ?? capitalize(key) + ) ) const emit = defineEmits<{ diff --git a/packages/hoppscotch-common/src/components/app/spotlight/entry/RESTRequest.vue b/packages/hoppscotch-common/src/components/app/spotlight/entry/RESTRequest.vue index 062b474598..0ba4cb15bc 100644 --- a/packages/hoppscotch-common/src/components/app/spotlight/entry/RESTRequest.vue +++ b/packages/hoppscotch-common/src/components/app/spotlight/entry/RESTRequest.vue @@ -7,9 +7,9 @@ {{ request.method.toUpperCase() }} diff --git a/packages/hoppscotch-common/src/components/collections/Add.vue b/packages/hoppscotch-common/src/components/collections/Add.vue index 2a39b7d4b8..4d2a81d424 100644 --- a/packages/hoppscotch-common/src/components/collections/Add.vue +++ b/packages/hoppscotch-common/src/components/collections/Add.vue @@ -69,6 +69,10 @@ watch( ) const addNewCollection = () => { + if (props.loadingState) { + return + } + if (!editingName.value) { toast.error(t("collection.invalid_name")) return diff --git a/packages/hoppscotch-common/src/components/collections/AddFolder.vue b/packages/hoppscotch-common/src/components/collections/AddFolder.vue index e8f3aaf154..30cec3b09a 100644 --- a/packages/hoppscotch-common/src/components/collections/AddFolder.vue +++ b/packages/hoppscotch-common/src/components/collections/AddFolder.vue @@ -69,10 +69,15 @@ watch( ) const addFolder = () => { + if (props.loadingState) { + return + } + if (editingName.value.trim() === "") { toast.error(t("folder.invalid_name")) return } + emit("add-folder", editingName.value) } diff --git a/packages/hoppscotch-common/src/components/collections/AddRequest.vue b/packages/hoppscotch-common/src/components/collections/AddRequest.vue index 02870f2ef5..1b4f19875a 100644 --- a/packages/hoppscotch-common/src/components/collections/AddRequest.vue +++ b/packages/hoppscotch-common/src/components/collections/AddRequest.vue @@ -3,32 +3,93 @@ v-if="show" dialog :title="t('request.new')" - @close="$emit('hide-modal')" + @close="hideModal" > - + @@ -39,6 +100,14 @@ import { useI18n } from "@composables/i18n" import { useToast } from "@composables/toast" import { useService } from "dioc/vue" import { RESTTabService } from "~/services/tab/rest" +import { + useRequestNameGeneration, + useSubmitFeedback, +} from "~/composables/ai-experiments" +import { HoppRESTRequest } from "@hoppscotch/data" +import IconSparkle from "~icons/lucide/sparkles" +import IconThumbsUp from "~icons/lucide/thumbs-up" +import IconThumbsDown from "~icons/lucide/thumbs-down" const toast = useToast() const t = useI18n() @@ -47,10 +116,12 @@ const props = withDefaults( defineProps<{ show: boolean loadingState: boolean + requestContext: HoppRESTRequest | null }>(), { show: false, loadingState: false, + requestContext: null, } ) @@ -61,21 +132,47 @@ const emit = defineEmits<{ const editingName = ref("") +const { + generateRequestName, + isGenerateRequestNamePending, + canDoRequestNameGeneration, + lastTraceID, +} = useRequestNameGeneration(editingName) + +watch( + () => props.show, + (newVal) => { + if (!newVal) { + submittedFeedback.value = false + lastTraceID.value = null + } + } +) + +const submittedFeedback = ref(false) +const { submitFeedback, isSubmitFeedbackPending } = useSubmitFeedback() + const tabs = useService(RESTTabService) watch( () => props.show, (show) => { if (show) { - editingName.value = tabs.currentActiveTab.value.document.request.name + if (tabs.currentActiveTab.value.document.type === "request") + editingName.value = tabs.currentActiveTab.value.document.request.name } } ) const addRequest = () => { + if (props.loadingState) { + return + } + if (editingName.value.trim() === "") { toast.error(`${t("error.empty_req_name")}`) return } + emit("add-request", editingName.value) } diff --git a/packages/hoppscotch-common/src/components/collections/Collection.vue b/packages/hoppscotch-common/src/components/collections/Collection.vue index 5847fec909..3de4eb4281 100644 --- a/packages/hoppscotch-common/src/components/collections/Collection.vue +++ b/packages/hoppscotch-common/src/components/collections/Collection.vue @@ -33,7 +33,7 @@ dropItemID = '' } " - @contextmenu.prevent="options?.tippy.show()" + @contextmenu.prevent="options?.tippy?.show()" >
@@ -73,6 +73,13 @@ class="hidden group-hover:inline-flex" @click="emit('add-folder')" /> +
+
+ +
@@ -164,6 +171,33 @@ @dragleave="resetDragState" @dragend="resetDragState" >
+ +
+
+
+ +
+
@@ -175,6 +209,8 @@ import IconCopy from "~icons/lucide/copy" import IconTrash2 from "~icons/lucide/trash-2" import IconRotateCCW from "~icons/lucide/rotate-ccw" import IconShare2 from "~icons/lucide/share-2" +import IconArrowRight from "~icons/lucide/chevron-right" +import IconArrowDown from "~icons/lucide/chevron-down" import { ref, PropType, watch, computed } from "vue" import { HoppRESTRequest } from "@hoppscotch/data" import { useI18n } from "@composables/i18n" @@ -211,7 +247,7 @@ const props = defineProps({ default: "my-collections", required: true, }, - duplicateLoading: { + duplicateRequestLoading: { type: Boolean, default: false, required: false, @@ -248,8 +284,14 @@ const props = defineProps({ }, }) +type ResponsePayload = { + responseName: string + responseID: string +} + const emit = defineEmits<{ (event: "edit-request"): void + (event: "edit-response", payload: ResponsePayload): void (event: "duplicate-request"): void (event: "remove-request"): void (event: "select-request"): void @@ -257,9 +299,13 @@ const emit = defineEmits<{ (event: "drag-request", payload: DataTransfer): void (event: "update-request-order", payload: DataTransfer): void (event: "update-last-request-order", payload: DataTransfer): void + (event: "duplicate-response", payload: ResponsePayload): void + (event: "remove-response", payload: ResponsePayload): void + (event: "select-response", payload: ResponsePayload): void + (event: "toggle-children"): void }>() -const tippyActions = ref(null) +const tippyActions = ref(null) const edit = ref(null) const deleteAction = ref(null) const options = ref(null) @@ -270,6 +316,8 @@ const dragging = ref(false) const ordering = ref(false) const orderingLastItem = ref(false) +const isResponseVisible = ref(false) + const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, { type: "collection", id: "", @@ -277,10 +325,10 @@ const currentReorderingStatus = useReadonlyStream(currentReorderingStatus$, { }) watch( - () => props.duplicateLoading, + () => props.duplicateRequestLoading, (val) => { if (!val) { - options.value!.tippy.hide() + options.value!.tippy?.hide() } } ) @@ -289,6 +337,11 @@ const selectRequest = () => { emit("select-request") } +const toggleRequestResponse = () => { + emit("toggle-children") + isResponseVisible.value = !isResponseVisible.value +} + const dragStart = ({ dataTransfer }: DragEvent) => { if (dataTransfer) { emit("drag-request", dataTransfer) diff --git a/packages/hoppscotch-common/src/components/collections/SaveRequest.vue b/packages/hoppscotch-common/src/components/collections/SaveRequest.vue index 0f440da489..54d1a93b56 100644 --- a/packages/hoppscotch-common/src/components/collections/SaveRequest.vue +++ b/packages/hoppscotch-common/src/components/collections/SaveRequest.vue @@ -8,14 +8,34 @@ > diff --git a/packages/hoppscotch-common/src/components/collections/graphql/Folder.vue b/packages/hoppscotch-common/src/components/collections/graphql/Folder.vue index 5852daddc8..552603fee5 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/Folder.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/Folder.vue @@ -70,7 +70,9 @@ @keyup.r="requestAction.$el.click()" @keyup.n="folderAction.$el.click()" @keyup.e="edit.$el.click()" + @keyup.d="duplicateAction.$el.click()" @keyup.delete="deleteAction.$el.click()" + @keyup.p="propertiesAction.$el.click()" @keyup.escape="hide()" > + +import { useI18n } from "@composables/i18n" +import { useColorMode } from "@composables/theming" +import { useToast } from "@composables/toast" +import { HoppCollection } from "@hoppscotch/data" +import { useService } from "dioc/vue" +import { computed, ref } from "vue" +import { Picked } from "~/helpers/types/HoppPicked" +import { removeGraphqlFolder } from "~/newstore/collections" +import { GQLTabService } from "~/services/tab/graphql" +import IconCheckCircle from "~icons/lucide/check-circle" +import IconCopy from "~icons/lucide/copy" import IconEdit from "~icons/lucide/edit" -import IconTrash2 from "~icons/lucide/trash-2" -import IconFolderPlus from "~icons/lucide/folder-plus" import IconFilePlus from "~icons/lucide/file-plus" -import IconMoreVertical from "~icons/lucide/more-vertical" -import IconCheckCircle from "~icons/lucide/check-circle" import IconFolder from "~icons/lucide/folder" import IconFolderOpen from "~icons/lucide/folder-open" +import IconFolderPlus from "~icons/lucide/folder-plus" +import IconMoreVertical from "~icons/lucide/more-vertical" import IconSettings2 from "~icons/lucide/settings-2" -import { useToast } from "@composables/toast" -import { useI18n } from "@composables/i18n" -import { useColorMode } from "@composables/theming" -import { removeGraphqlFolder } from "~/newstore/collections" -import { computed, ref } from "vue" -import { useService } from "dioc/vue" -import { GQLTabService } from "~/services/tab/graphql" -import { Picked } from "~/helpers/types/HoppPicked" -import { HoppCollection } from "@hoppscotch/data" +import IconTrash2 from "~icons/lucide/trash-2" const toast = useToast() const t = useI18n() @@ -255,6 +274,7 @@ const emit = defineEmits([ "edit-request", "add-folder", "edit-folder", + "duplicate-collection", "duplicate-request", "edit-properties", "select-request", @@ -267,7 +287,9 @@ const options = ref(null) const requestAction = ref(null) const folderAction = ref(null) const edit = ref(null) +const duplicateAction = ref(null) const deleteAction = ref(null) +const propertiesAction = ref(null) const showChildren = ref(false) const dragging = ref(false) diff --git a/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue b/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue index a9dc667b83..b0759eabe0 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/ImportExport.vue @@ -21,7 +21,7 @@ import { GistSource } from "~/helpers/import-export/import/import-sources/GistSo import IconFolderPlus from "~icons/lucide/folder-plus" import IconUser from "~icons/lucide/user" -import { initializeDownloadCollection } from "~/helpers/import-export/export" +import { initializeDownloadFile } from "~/helpers/import-export/export" import { useReadonlyStream } from "~/composables/stream" import { platform } from "~/platform" @@ -133,14 +133,14 @@ const GqlCollectionsHoppExporter: ImporterOrExporter = { disabled: false, applicableTo: ["personal-workspace", "team-workspace"], }, - action: () => { + action: async () => { if (!gqlCollections.value.length) { return toast.error(t("error.no_collections_to_export")) } - const message = initializeDownloadCollection( + const message = await initializeDownloadFile( gqlCollectionsExporter(gqlCollections.value), - "GQLCollections" + "hoppscotch-gql-collections" ) if (E.isLeft(message)) { @@ -148,7 +148,7 @@ const GqlCollectionsHoppExporter: ImporterOrExporter = { return } - toast.success(message.right) + toast.success(t("state.download_started")) platform.analytics?.logEvent({ type: "HOPP_EXPORT_COLLECTION", @@ -231,7 +231,7 @@ const showImportFailedError = () => { toast.error(t("import.failed")) } -const handleImportToStore = async (gqlCollections: HoppCollection[]) => { +const handleImportToStore = (gqlCollections: HoppCollection[]) => { appendGraphqlCollections(gqlCollections) toast.success(t("state.file_imported")) } diff --git a/packages/hoppscotch-common/src/components/collections/graphql/index.vue b/packages/hoppscotch-common/src/components/collections/graphql/index.vue index bf95552b4c..406cade55d 100644 --- a/packages/hoppscotch-common/src/components/collections/graphql/index.vue +++ b/packages/hoppscotch-common/src/components/collections/graphql/index.vue @@ -54,6 +54,7 @@ @add-request="addRequest($event)" @add-folder="addFolder($event)" @edit-folder="editFolder($event)" + @duplicate-collection="duplicateCollection($event)" @edit-request="editRequest($event)" @duplicate-request="duplicateRequest($event)" @select-collection="$emit('use-collection', collection)" @@ -115,6 +116,7 @@ @@ -138,6 +140,7 @@ :folder-path="editingFolderPath" :request="editingRequest" :request-index="editingRequestIndex" + :request-context="editingRequest" :editing-request-name="editingRequest ? editingRequest.name : ''" @hide-modal="displayModalEditRequest(false)" /> @@ -167,6 +170,7 @@ import { editGraphqlCollection, editGraphqlFolder, moveGraphqlRequest, + duplicateGraphQLCollection, } from "~/newstore/collections" import IconPlus from "~icons/lucide/plus" import IconHelpCircle from "~icons/lucide/help-circle" @@ -178,11 +182,7 @@ import { platform } from "~/platform" import { useService } from "dioc/vue" import { GQLTabService } from "~/services/tab/graphql" import { computed } from "vue" -import { - HoppCollection, - HoppGQLRequest, - makeGQLRequest, -} from "@hoppscotch/data" +import { HoppCollection, HoppGQLRequest } from "@hoppscotch/data" import { Picked } from "~/helpers/types/HoppPicked" import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties" import { updateInheritedPropertiesForAffectedRequests } from "~/helpers/collection/collection" @@ -192,6 +192,7 @@ import { PersistenceService } from "~/services/persistence" import { PersistedOAuthConfig } from "~/services/oauth/oauth.service" import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue" import { EditingProperties } from "../Properties.vue" +import { defineActionHandler } from "~/helpers/actions" const t = useI18n() const toast = useToast() @@ -199,7 +200,7 @@ const toast = useToast() defineProps<{ // Whether to activate the ability to pick items (activates 'select' events) saveRequest: boolean - picked: Picked + picked: Picked | null }>() const collections = useReadonlyStream(graphqlCollections$, [], "deep") @@ -250,7 +251,7 @@ onMounted(() => { return } - const { context, source, token }: PersistedOAuthConfig = + const { context, source, token, refresh_token }: PersistedOAuthConfig = JSON.parse(localOAuthTempConfig) if (source === "REST") { @@ -274,6 +275,10 @@ onMounted(() => { const grantTypeInfo = auth.grantTypeInfo grantTypeInfo && (grantTypeInfo.token = token ?? "") + + if (refresh_token && grantTypeInfo.grantType === "AUTHORIZATION_CODE") { + grantTypeInfo.refreshToken = refresh_token + } } editingProperties.value = unsavedCollectionProperties @@ -326,6 +331,10 @@ const filteredCollections = computed(() => { return filteredCollections }) +const requestContext = computed(() => { + return tabs.currentActiveTab.value.document.request +}) + const displayModalAdd = (shouldDisplay: boolean) => { showModalAdd.value = shouldDisplay } @@ -379,21 +388,21 @@ const editCollection = ( displayModalEdit(true) } -const onAddRequest = ({ - name, +const duplicateCollection = ({ path, - index, + collectionSyncID, }: { - name: string path: string - index: number -}) => { + collectionSyncID?: string +}) => duplicateGraphQLCollection(path, collectionSyncID) + +const onAddRequest = ({ name, path }: { name: string; path: string }) => { const newRequest = { ...tabs.currentActiveTab.value.document.request, name, } - saveGraphqlRequestAs(path, newRequest) + const insertionIndex = saveGraphqlRequestAs(path, newRequest) const { auth, headers } = cascadeParentCollectionForHeaderAuth( path, @@ -404,7 +413,7 @@ const onAddRequest = ({ saveContext: { originLocation: "user-collection", folderPath: path, - requestIndex: index, + requestIndex: insertionIndex, }, request: newRequest, isDirty: false, @@ -526,23 +535,13 @@ const selectRequest = ({ tabs.setActiveTab(possibleTab.value.id) return } - tabs.createNewTab({ saveContext: { originLocation: "user-collection", folderPath: folderPath, requestIndex: requestIndex, }, - request: cloneDeep( - makeGQLRequest({ - name: request.name, - url: request.url, - query: request.query, - headers: request.headers, - variables: request.variables, - auth: request.auth, - }) - ), + request: cloneDeep(request), isDirty: false, inheritedProperties: { auth, @@ -676,4 +675,11 @@ const resetSelectedData = () => { editingRequest.value = null editingRequestIndex.value = null } + +defineActionHandler("collection.new", () => { + displayModalAdd(true) +}) +defineActionHandler("modals.collection.import", () => { + displayModalImportExport(true) +}) diff --git a/packages/hoppscotch-common/src/components/collections/index.vue b/packages/hoppscotch-common/src/components/collections/index.vue index db0b4f1925..6c083b6048 100644 --- a/packages/hoppscotch-common/src/components/collections/index.vue +++ b/packages/hoppscotch-common/src/components/collections/index.vue @@ -33,26 +33,38 @@ :filter-text="filterTexts" :save-request="saveRequest" :picked="picked" + @run-collection=" + runCollectionHandler({ + type: 'my-collections', + collectionID: $event.collection._ref_id, + collectionIndex: $event.collectionIndex, + }) + " @add-folder="addFolder" @add-request="addRequest" + @edit-request="editRequest" @edit-collection="editCollection" @edit-folder="editFolder" + @edit-response="editResponse" + @drop-request="dropRequest" + @drop-collection="dropCollection" + @display-modal-add="displayModalAdd(true)" + @display-modal-import-export="displayModalImportExport(true)" + @duplicate-collection="duplicateCollection" + @duplicate-request="duplicateRequest" + @duplicate-response="duplicateResponse" @edit-properties="editProperties" @export-data="exportData" @remove-collection="removeCollection" @remove-folder="removeFolder" + @remove-request="removeRequest" + @remove-response="removeResponse" @share-request="shareRequest" - @drop-collection="dropCollection" + @select="selectPicked" + @select-response="selectResponse" + @select-request="selectRequest" @update-request-order="updateRequestOrder" @update-collection-order="updateCollectionOrder" - @edit-request="editRequest" - @duplicate-request="duplicateRequest" - @remove-request="removeRequest" - @select-request="selectRequest" - @select="selectPicked" - @drop-request="dropRequest" - @display-modal-add="displayModalAdd(true)" - @display-modal-import-export="displayModalImportExport(true)" /> diff --git a/packages/hoppscotch-common/src/components/cookies/EditCookie.vue b/packages/hoppscotch-common/src/components/cookies/EditCookie.vue index 79c80f1a08..bd9503e0cc 100644 --- a/packages/hoppscotch-common/src/components/cookies/EditCookie.vue +++ b/packages/hoppscotch-common/src/components/cookies/EditCookie.vue @@ -187,8 +187,9 @@ function saveCookieChange() { const { copyIcon, copyResponse } = useCopyResponse(rawCookieString) const { downloadIcon, downloadResponse } = useDownloadResponse( - "", - rawCookieString + "text/plain", + rawCookieString, + t("filename.cookie_key_value_pairs") ) function clearContent() { diff --git a/packages/hoppscotch-common/src/components/embeds/Header.vue b/packages/hoppscotch-common/src/components/embeds/Header.vue new file mode 100644 index 0000000000..8cd6d2de2f --- /dev/null +++ b/packages/hoppscotch-common/src/components/embeds/Header.vue @@ -0,0 +1,33 @@ + + + diff --git a/packages/hoppscotch-common/src/components/embeds/Request.vue b/packages/hoppscotch-common/src/components/embeds/Request.vue new file mode 100644 index 0000000000..c072c3b23b --- /dev/null +++ b/packages/hoppscotch-common/src/components/embeds/Request.vue @@ -0,0 +1,185 @@ + + + diff --git a/packages/hoppscotch-common/src/components/embeds/index.vue b/packages/hoppscotch-common/src/components/embeds/index.vue index adbcedf784..a5d57f2e19 100644 --- a/packages/hoppscotch-common/src/components/embeds/index.vue +++ b/packages/hoppscotch-common/src/components/embeds/index.vue @@ -1,108 +1,37 @@ diff --git a/packages/hoppscotch-common/src/components/environments/Add.vue b/packages/hoppscotch-common/src/components/environments/Add.vue index 3446d2349f..5eed9be636 100644 --- a/packages/hoppscotch-common/src/components/environments/Add.vue +++ b/packages/hoppscotch-common/src/components/environments/Add.vue @@ -71,20 +71,21 @@ diff --git a/packages/hoppscotch-common/src/components/environments/ImportExport.vue b/packages/hoppscotch-common/src/components/environments/ImportExport.vue index fc97315f4c..337c2aac38 100644 --- a/packages/hoppscotch-common/src/components/environments/ImportExport.vue +++ b/packages/hoppscotch-common/src/components/environments/ImportExport.vue @@ -13,36 +13,37 @@ import { Environment, NonSecretEnvironment } from "@hoppscotch/data" import * as E from "fp-ts/Either" import { ref } from "vue" +import { ImporterOrExporter } from "~/components/importExport/types" import { useI18n } from "~/composables/i18n" import { useToast } from "~/composables/toast" -import { ImporterOrExporter } from "~/components/importExport/types" +import { hoppEnvImporter } from "~/helpers/import-export/import/hoppEnv" import { FileSource } from "~/helpers/import-export/import/import-sources/FileSource" import { GistSource } from "~/helpers/import-export/import/import-sources/GistSource" -import { hoppEnvImporter } from "~/helpers/import-export/import/hoppEnv" import { - appendEnvironments, addGlobalEnvVariable, + appendEnvironments, environments$, } from "~/newstore/environments" -import { createTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment" -import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment" import { GQLError } from "~/helpers/backend/GQLClient" import { CreateTeamEnvironmentMutation } from "~/helpers/backend/graphql" -import { postmanEnvImporter } from "~/helpers/import-export/import/postmanEnv" +import { createTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment" import { insomniaEnvImporter } from "~/helpers/import-export/import/insomniaEnv" +import { postmanEnvImporter } from "~/helpers/import-export/import/postmanEnv" +import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment" -import IconFolderPlus from "~icons/lucide/folder-plus" -import IconPostman from "~icons/hopp/postman" -import IconInsomnia from "~icons/hopp/insomnia" -import IconUser from "~icons/lucide/user" -import { initializeDownloadCollection } from "~/helpers/import-export/export" import { computed } from "vue" import { useReadonlyStream } from "~/composables/stream" +import { initializeDownloadFile } from "~/helpers/import-export/export" +import { transformEnvironmentVariables } from "~/helpers/import-export/export/environment" import { environmentsExporter } from "~/helpers/import-export/export/environments" import { gistExporter } from "~/helpers/import-export/export/gist" import { platform } from "~/platform" +import IconInsomnia from "~icons/hopp/insomnia" +import IconPostman from "~icons/hopp/postman" +import IconFolderPlus from "~icons/lucide/folder-plus" +import IconUser from "~icons/lucide/user" const t = useI18n() const toast = useToast() @@ -60,6 +61,11 @@ const currentUser = useReadonlyStream( platform.auth.getCurrentUser() ) +const isPostmanImporterInProgress = ref(false) +const isInsomniaImporterInProgress = ref(false) +const isRESTImporterInProgress = ref(false) +const isGistImporterInProgress = ref(false) + const isEnvironmentGistExportInProgress = ref(false) const isTeamEnvironment = computed(() => { @@ -67,19 +73,19 @@ const isTeamEnvironment = computed(() => { }) const environmentJson = computed(() => { - if ( - props.environmentType === "TEAM_ENV" && - props.teamEnvironments !== undefined - ) { - const teamEnvironments = props.teamEnvironments.map( - (x) => x.environment as Environment + if (isTeamEnvironment.value && props.teamEnvironments) { + return props.teamEnvironments.map(({ environment }) => + transformEnvironmentVariables(environment) ) - return teamEnvironments } - return myEnvironments.value + return myEnvironments.value.map(transformEnvironmentVariables) }) +const workspaceType = computed(() => + isTeamEnvironment.value ? "team" : "personal" +) + const HoppEnvironmentsImport: ImporterOrExporter = { metadata: { id: "import.from_json", @@ -93,23 +99,27 @@ const HoppEnvironmentsImport: ImporterOrExporter = { acceptedFileTypes: "application/json", caption: "import.hoppscotch_environment_description", onImportFromFile: async (environments) => { + isRESTImporterInProgress.value = true + const res = await hoppEnvImporter(environments)() - if (E.isLeft(res)) { - showImportFailedError() - return - } + if (E.isRight(res)) { + await handleImportToStore(res.right) - handleImportToStore(res.right) + platform.analytics?.logEvent({ + type: "HOPP_IMPORT_ENVIRONMENT", + platform: "rest", + workspaceType: isTeamEnvironment.value ? "team" : "personal", + }) - platform.analytics?.logEvent({ - type: "HOPP_IMPORT_ENVIRONMENT", - platform: "rest", - workspaceType: isTeamEnvironment.value ? "team" : "personal", - }) + emit("hide-modal") + } else { + showImportFailedError() + } - emit("hide-modal") + isRESTImporterInProgress.value = false }, + isLoading: isRESTImporterInProgress, }), } @@ -126,23 +136,27 @@ const PostmanEnvironmentsImport: ImporterOrExporter = { acceptedFileTypes: "application/json", caption: "import.postman_environment_description", onImportFromFile: async (environments) => { + isPostmanImporterInProgress.value = true + const res = await postmanEnvImporter(environments)() - if (E.isLeft(res)) { - showImportFailedError() - return - } + if (E.isRight(res)) { + await handleImportToStore(res.right) - handleImportToStore(res.right) + platform.analytics?.logEvent({ + type: "HOPP_IMPORT_ENVIRONMENT", + platform: "rest", + workspaceType: isTeamEnvironment.value ? "team" : "personal", + }) - platform.analytics?.logEvent({ - type: "HOPP_IMPORT_ENVIRONMENT", - platform: "rest", - workspaceType: isTeamEnvironment.value ? "team" : "personal", - }) + emit("hide-modal") + } else { + showImportFailedError() + } - emit("hide-modal") + isPostmanImporterInProgress.value = false }, + isLoading: isPostmanImporterInProgress, }), } @@ -159,30 +173,34 @@ const insomniaEnvironmentsImport: ImporterOrExporter = { acceptedFileTypes: "application/json", caption: "import.insomnia_environment_description", onImportFromFile: async (environments) => { + isInsomniaImporterInProgress.value = true + const res = await insomniaEnvImporter(environments)() - if (E.isLeft(res)) { - showImportFailedError() - return - } + if (E.isRight(res)) { + const globalEnvs = res.right.filter( + (env) => env.name === "Base Environment" + ) + const otherEnvs = res.right.filter( + (env) => env.name !== "Base Environment" + ) - const globalEnvs = res.right.filter( - (env) => env.name === "Base Environment" - ) - const otherEnvs = res.right.filter( - (env) => env.name !== "Base Environment" - ) + await handleImportToStore(otherEnvs, globalEnvs) - handleImportToStore(otherEnvs, globalEnvs) + platform.analytics?.logEvent({ + type: "HOPP_IMPORT_ENVIRONMENT", + platform: "rest", + workspaceType: isTeamEnvironment.value ? "team" : "personal", + }) - platform.analytics?.logEvent({ - type: "HOPP_IMPORT_ENVIRONMENT", - platform: "rest", - workspaceType: isTeamEnvironment.value ? "team" : "personal", - }) + emit("hide-modal") + } else { + showImportFailedError() + } - emit("hide-modal") + isInsomniaImporterInProgress.value = false }, + isLoading: isInsomniaImporterInProgress, }), } @@ -203,21 +221,26 @@ const EnvironmentsImportFromGIST: ImporterOrExporter = { return } + isGistImporterInProgress.value = true + const res = await hoppEnvImporter(environments.right)() - if (E.isLeft(res)) { + if (E.isRight(res)) { + await handleImportToStore(res.right) + + platform.analytics?.logEvent({ + type: "HOPP_IMPORT_ENVIRONMENT", + platform: "rest", + workspaceType: isTeamEnvironment.value ? "team" : "personal", + }) + emit("hide-modal") + } else { showImportFailedError() - return } - handleImportToStore(res.right) - platform.analytics?.logEvent({ - type: "HOPP_IMPORT_ENVIRONMENT", - platform: "rest", - workspaceType: isTeamEnvironment.value ? "team" : "personal", - }) - emit("hide-modal") + isGistImporterInProgress.value = false }, + isLoading: isGistImporterInProgress, }), } @@ -230,22 +253,22 @@ const HoppEnvironmentsExport: ImporterOrExporter = { disabled: false, applicableTo: ["personal-workspace", "team-workspace"], }, - action: () => { + action: async () => { if (!environmentJson.value.length) { return toast.error(t("error.no_environments_to_export")) } - const message = initializeDownloadCollection( + const message = await initializeDownloadFile( environmentsExporter(environmentJson.value), - "Environments" + `hoppscotch-${workspaceType.value}-environments` ) if (E.isLeft(message)) { - toast.error(t(message.left)) + toast.error(t("export.failed")) return } - toast.success(t(message.right)) + toast.success(t("state.download_started")) platform.analytics?.logEvent({ type: "HOPP_EXPORT_ENVIRONMENT", diff --git a/packages/hoppscotch-common/src/components/environments/Properties.vue b/packages/hoppscotch-common/src/components/environments/Properties.vue new file mode 100644 index 0000000000..ede8f811cb --- /dev/null +++ b/packages/hoppscotch-common/src/components/environments/Properties.vue @@ -0,0 +1,122 @@ + + + diff --git a/packages/hoppscotch-common/src/components/environments/Selector.vue b/packages/hoppscotch-common/src/components/environments/Selector.vue index f5e463b367..d0e511cd13 100644 --- a/packages/hoppscotch-common/src/components/environments/Selector.vue +++ b/packages/hoppscotch-common/src/components/environments/Selector.vue @@ -77,24 +77,27 @@ :label="`${t('environment.my_environments')}`" >
- {{ getErrorMessage(teamAdapterError) }} + {{ t(getEnvActionErrorMessage(teamAdapterError)) }}
@@ -202,7 +205,7 @@
@@ -216,7 +219,10 @@
-
+
{{ t("environment.empty_variables") }}
@@ -289,33 +295,36 @@ diff --git a/packages/hoppscotch-common/src/components/environments/my/index.vue b/packages/hoppscotch-common/src/components/environments/my/index.vue index 9eaa2f00ce..23fdef6677 100644 --- a/packages/hoppscotch-common/src/components/environments/my/index.vue +++ b/packages/hoppscotch-common/src/components/environments/my/index.vue @@ -26,14 +26,14 @@
diff --git a/packages/hoppscotch-common/src/components/environments/teams/Environment.vue b/packages/hoppscotch-common/src/components/environments/teams/Environment.vue index 2271a997bb..a7e74f35c4 100644 --- a/packages/hoppscotch-common/src/components/environments/teams/Environment.vue +++ b/packages/hoppscotch-common/src/components/environments/teams/Environment.vue @@ -1,7 +1,7 @@
@@ -108,26 +121,30 @@ diff --git a/packages/hoppscotch-common/src/components/environments/teams/index.vue b/packages/hoppscotch-common/src/components/environments/teams/index.vue index b8aa2bfdf0..a69482bb2b 100644 --- a/packages/hoppscotch-common/src/components/environments/teams/index.vue +++ b/packages/hoppscotch-common/src/components/environments/teams/index.vue @@ -4,7 +4,7 @@ class="sticky top-upperPrimaryStickyFold z-10 flex flex-1 flex-shrink-0 justify-between overflow-x-auto border-b border-dividerLight bg-primary" >
@@ -97,25 +104,33 @@ class="flex flex-col items-center py-4" > - {{ getErrorMessage(adapterError) }} + {{ t(getEnvActionErrorMessage(adapterError)) }}
+
@@ -129,21 +144,27 @@ import IconPlus from "~icons/lucide/plus" import IconHelpCircle from "~icons/lucide/help-circle" import IconImport from "~icons/lucide/folder-down" import { defineActionHandler } from "~/helpers/actions" -import { GetMyTeamsQuery } from "~/helpers/backend/graphql" +import { TeamWorkspace } from "~/services/workspace.service" +import { sortTeamEnvironmentsAlphabetically } from "~/helpers/utils/sortEnvironmentsAlphabetically" +import { getEnvActionErrorMessage } from "~/helpers/error-messages" const t = useI18n() const colorMode = useColorMode() -type SelectedTeam = GetMyTeamsQuery["myTeams"][number] | undefined - const props = defineProps<{ - team: SelectedTeam + team: TeamWorkspace | undefined teamEnvironments: TeamEnvironment[] adapterError: GQLError | null loading: boolean }>() +// Sort environments alphabetically by default + +const alphabeticallySortedTeamEnvironments = computed(() => + sortTeamEnvironmentsAlphabetically(props.teamEnvironments, "asc") +) + const showModalImportExport = ref(false) const showModalDetails = ref(false) const action = ref<"new" | "edit">("edit") @@ -151,7 +172,11 @@ const editingEnvironment = ref(null) const editingVariableName = ref("") const secretOptionSelected = ref(false) -const isTeamViewer = computed(() => props.team?.myRole === "VIEWER") +const showEnvironmentsPropertiesModal = ref(false) +const environmentsPropertiesModalActiveTab = ref("details") +const selectedEnvironmentID = ref(null) + +const isTeamViewer = computed(() => props.team?.role === "VIEWER") const displayModalAdd = (shouldDisplay: boolean) => { action.value = "new" @@ -177,27 +202,21 @@ const resetSelectedData = () => { secretOptionSelected.value = false } -const getErrorMessage = (err: GQLError) => { - if (err.type === "network_error") { - return t("error.network_error") - } - switch (err.error) { - case "team_environment/not_found": - return t("team_environment.not_found") - default: - return t("error.something_went_wrong") - } +const showEnvironmentProperties = (environmentID: string) => { + showEnvironmentsPropertiesModal.value = true + selectedEnvironmentID.value = environmentID } defineActionHandler( "modals.team.environment.edit", ({ envName, variableName, isSecret }) => { if (variableName) editingVariableName.value = variableName - const teamEnvToEdit = props.teamEnvironments.find( - (environment) => environment.environment.name === envName + const teamEnvToEdit = alphabeticallySortedTeamEnvironments.value.find( + ({ env }) => env.environment.name === envName ) if (teamEnvToEdit) { - editEnvironment(teamEnvToEdit) + const { env } = teamEnvToEdit + editEnvironment(env) secretOptionSelected.value = isSecret ?? false } } diff --git a/packages/hoppscotch-common/src/components/firebase/Login.vue b/packages/hoppscotch-common/src/components/firebase/Login.vue index 0a55ad3721..4ffd4d3306 100644 --- a/packages/hoppscotch-common/src/components/firebase/Login.vue +++ b/packages/hoppscotch-common/src/components/firebase/Login.vue @@ -6,7 +6,11 @@ @close="hideModal" >