mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-27 17:09:59 +02:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf6ef24902 | |||
| 258ea047b6 | |||
| c62ac6288e | |||
| 2b583d1844 | |||
| cff3f521c1 | |||
| 404e12dc2d | |||
| f9de75db0a | |||
| 83b7bf2e2f | |||
| d81add6979 | |||
| 5cf5389aad | |||
| 943b3b849a | |||
| f54b6ad2d2 | |||
| 4da80dd2db | |||
| 17a9b7c3f2 | |||
| 001bda2efd | |||
| ff401fd4d3 | |||
| 82a2efa7f2 | |||
| 9fe973039d | |||
| 2cdbdaa1ab | |||
| d31b22f57d |
@@ -34,7 +34,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
|
||||
- name: Set up pnpm package manager
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 #v4.4.0
|
||||
uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb #v6.0.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
echo "Tags: ${TAGS}"
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 #v7.0.0
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f #v7.1.0
|
||||
with:
|
||||
context: .
|
||||
file: ./donut-sync/Dockerfile
|
||||
|
||||
@@ -327,7 +327,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
|
||||
- name: Run opencode
|
||||
uses: anomalyco/opencode/github@6314f09c14fdd6a3ab8bedc4f7b7182647551d12 #v1.3.13
|
||||
uses: anomalyco/opencode/github@877be7e8e04142cd8fbebcb5e6c4b9617bf28cce #v1.4.3
|
||||
env:
|
||||
ZHIPU_API_KEY: ${{ secrets.ZHIPU_API_KEY }}
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
|
||||
- name: Set up pnpm package manager
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 #v4.4.0
|
||||
uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb #v6.0.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
|
||||
- name: Set up pnpm package manager
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 #v4.4.0
|
||||
uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb #v6.0.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
|
||||
@@ -59,7 +59,14 @@ jobs:
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dpkg-dev createrepo-c
|
||||
sudo apt-get install -y dpkg-dev createrepo-c python3-pip
|
||||
# Install aws-cli v1 via pip, overriding the pre-installed v2.
|
||||
# aws-cli v2.23+ sends CRC64NVME checksums that Cloudflare R2 rejects
|
||||
# with Unauthorized, and the s3transfer lib used by `aws s3 sync` has
|
||||
# a confirmed bug where WHEN_REQUIRED env var is silently ignored
|
||||
# (boto/s3transfer#327). aws-cli v1 never sends these headers at all
|
||||
# and matches the proven local Docker publish path.
|
||||
pip3 install --break-system-packages awscli
|
||||
|
||||
- name: Download packages from GitHub release
|
||||
env:
|
||||
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 #v4.4.0
|
||||
uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb #v6.0.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
@@ -545,7 +545,7 @@ jobs:
|
||||
|
||||
update-flake:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
needs: [release]
|
||||
needs: [release, changelog]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 #v4.4.0
|
||||
uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb #v6.0.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 #v4.4.0
|
||||
uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb #v6.0.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 #v4.4.0
|
||||
uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb #v6.0.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
|
||||
@@ -1,6 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## v0.20.3 (2026-04-10)
|
||||
|
||||
### Refactoring
|
||||
|
||||
- debug wayfern launch
|
||||
|
||||
### Maintenance
|
||||
|
||||
- chore: version bump
|
||||
- chore: serialize changelog and flake jobs
|
||||
- chore: update flake.nix for v0.20.2 [skip ci] (#273)
|
||||
|
||||
|
||||
## v0.20.2 (2026-04-08)
|
||||
|
||||
### Maintenance
|
||||
|
||||
- chore: version bump
|
||||
- chore: aws integrity checks
|
||||
- chore: inject NEXT_PUBLIC_TURNSTILE everywhere
|
||||
- chore: update flake.nix for v0.20.1 [skip ci] (#272)
|
||||
|
||||
|
||||
## v0.20.1 (2026-04-08)
|
||||
|
||||
### Maintenance
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
| | Apple Silicon | Intel |
|
||||
|---|---|---|
|
||||
| **DMG** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut_0.20.1_aarch64.dmg) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut_0.20.1_x64.dmg) |
|
||||
| **DMG** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut_0.20.3_aarch64.dmg) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut_0.20.3_x64.dmg) |
|
||||
|
||||
Or install via Homebrew:
|
||||
|
||||
@@ -61,15 +61,15 @@ brew install --cask donut
|
||||
|
||||
### Windows
|
||||
|
||||
[Download Windows Installer (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut_0.20.1_x64-setup.exe) · [Portable (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut_0.20.1_x64-portable.zip)
|
||||
[Download Windows Installer (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut_0.20.3_x64-setup.exe) · [Portable (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut_0.20.3_x64-portable.zip)
|
||||
|
||||
### Linux
|
||||
|
||||
| Format | x86_64 | ARM64 |
|
||||
|---|---|---|
|
||||
| **deb** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut_0.20.1_amd64.deb) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut_0.20.1_arm64.deb) |
|
||||
| **rpm** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut-0.20.1-1.x86_64.rpm) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut-0.20.1-1.aarch64.rpm) |
|
||||
| **AppImage** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut_0.20.1_amd64.AppImage) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut_0.20.1_aarch64.AppImage) |
|
||||
| **deb** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut_0.20.3_amd64.deb) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut_0.20.3_arm64.deb) |
|
||||
| **rpm** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut-0.20.3-1.x86_64.rpm) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut-0.20.3-1.aarch64.rpm) |
|
||||
| **AppImage** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut_0.20.3_amd64.AppImage) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut_0.20.3_aarch64.AppImage) |
|
||||
<!-- install-links-end -->
|
||||
|
||||
Or install via package manager:
|
||||
|
||||
@@ -94,17 +94,17 @@
|
||||
pkgConfigPath = lib.makeSearchPath "lib/pkgconfig" (
|
||||
pkgConfigLibs ++ map lib.getDev pkgConfigLibs
|
||||
);
|
||||
releaseVersion = "0.20.1";
|
||||
releaseVersion = "0.20.3";
|
||||
releaseAppImage =
|
||||
if system == "x86_64-linux" then
|
||||
pkgs.fetchurl {
|
||||
url = "https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut_0.20.1_amd64.AppImage";
|
||||
hash = "sha256-3uHIXtSVoS/sLY9EduuvgWjefT5rb9G/4Vzb95B0CPU=";
|
||||
url = "https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut_0.20.3_amd64.AppImage";
|
||||
hash = "sha256-w0+DdP12xVWUE3TOPrW/CxsaYh19zdROmVGGxVzB0lI=";
|
||||
}
|
||||
else if system == "aarch64-linux" then
|
||||
pkgs.fetchurl {
|
||||
url = "https://github.com/zhom/donutbrowser/releases/download/v0.20.1/Donut_0.20.1_aarch64.AppImage";
|
||||
hash = "sha256-fIduBF/eEX7liYnjWxrc+zJ1zharcHnlBq2ydUZu1r4=";
|
||||
url = "https://github.com/zhom/donutbrowser/releases/download/v0.20.3/Donut_0.20.3_aarch64.AppImage";
|
||||
hash = "sha256-3X02E8cS0tbxPQLmLnIbt6ngZVBwpIVaVkIRzku9zOE=";
|
||||
}
|
||||
else
|
||||
null;
|
||||
|
||||
+5
-5
@@ -2,7 +2,7 @@
|
||||
"name": "donutbrowser",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"version": "0.20.2",
|
||||
"version": "0.20.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack -p 12341",
|
||||
@@ -48,8 +48,8 @@
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@tauri-apps/api": "~2.10.1",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.7",
|
||||
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||
"@tauri-apps/plugin-fs": "~2.4.5",
|
||||
"@tauri-apps/plugin-dialog": "^2.7.0",
|
||||
"@tauri-apps/plugin-fs": "~2.5.0",
|
||||
"@tauri-apps/plugin-log": "^2.8.0",
|
||||
"@tauri-apps/plugin-opener": "^2.5.3",
|
||||
"ahooks": "^3.9.7",
|
||||
@@ -61,7 +61,7 @@
|
||||
"i18next": "^26.0.3",
|
||||
"lucide-react": "^1.7.0",
|
||||
"motion": "^12.38.0",
|
||||
"next": "^16.2.2",
|
||||
"next": "^16.2.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19.2.4",
|
||||
@@ -95,7 +95,7 @@
|
||||
"path-to-regexp@>=8.0.0 <8.4.0": ">=8.4.0"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.30.1",
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
"lint-staged": {
|
||||
"**/*.{js,jsx,ts,tsx,json,css}": [
|
||||
"biome check --fix"
|
||||
|
||||
Generated
+81
-71
@@ -58,11 +58,11 @@ importers:
|
||||
specifier: ^2.4.7
|
||||
version: 2.4.7
|
||||
'@tauri-apps/plugin-dialog':
|
||||
specifier: ^2.6.0
|
||||
version: 2.6.0
|
||||
specifier: ^2.7.0
|
||||
version: 2.7.0
|
||||
'@tauri-apps/plugin-fs':
|
||||
specifier: ~2.4.5
|
||||
version: 2.4.5
|
||||
specifier: ~2.5.0
|
||||
version: 2.5.0
|
||||
'@tauri-apps/plugin-log':
|
||||
specifier: ^2.8.0
|
||||
version: 2.8.0
|
||||
@@ -97,8 +97,8 @@ importers:
|
||||
specifier: ^12.38.0
|
||||
version: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
next:
|
||||
specifier: ^16.2.2
|
||||
version: 16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: ^16.2.3
|
||||
version: 16.2.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
next-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -696,8 +696,8 @@ packages:
|
||||
'@emnapi/core@1.9.1':
|
||||
resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==}
|
||||
|
||||
'@emnapi/runtime@1.9.1':
|
||||
resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==}
|
||||
'@emnapi/runtime@1.9.2':
|
||||
resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==}
|
||||
|
||||
'@emnapi/wasi-threads@1.2.0':
|
||||
resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==}
|
||||
@@ -1366,57 +1366,57 @@ packages:
|
||||
'@nestjs/platform-express':
|
||||
optional: true
|
||||
|
||||
'@next/env@16.2.2':
|
||||
resolution: {integrity: sha512-LqSGz5+xGk9EL/iBDr2yo/CgNQV6cFsNhRR2xhSXYh7B/hb4nePCxlmDvGEKG30NMHDFf0raqSyOZiQrO7BkHQ==}
|
||||
'@next/env@16.2.3':
|
||||
resolution: {integrity: sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==}
|
||||
|
||||
'@next/swc-darwin-arm64@16.2.2':
|
||||
resolution: {integrity: sha512-B92G3ulrwmkDSEJEp9+XzGLex5wC1knrmCSIylyVeiAtCIfvEJYiN3v5kXPlYt5R4RFlsfO/v++aKV63Acrugg==}
|
||||
'@next/swc-darwin-arm64@16.2.3':
|
||||
resolution: {integrity: sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@16.2.2':
|
||||
resolution: {integrity: sha512-7ZwSgNKJNQiwW0CKhNm9B1WS2L1Olc4B2XY0hPYCAL3epFnugMhuw5TMWzMilQ3QCZcCHoYm9NGWTHbr5REFxw==}
|
||||
'@next/swc-darwin-x64@16.2.3':
|
||||
resolution: {integrity: sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.2.2':
|
||||
resolution: {integrity: sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q==}
|
||||
'@next/swc-linux-arm64-gnu@16.2.3':
|
||||
resolution: {integrity: sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.2.2':
|
||||
resolution: {integrity: sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg==}
|
||||
'@next/swc-linux-arm64-musl@16.2.3':
|
||||
resolution: {integrity: sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.2.2':
|
||||
resolution: {integrity: sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g==}
|
||||
'@next/swc-linux-x64-gnu@16.2.3':
|
||||
resolution: {integrity: sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-musl@16.2.2':
|
||||
resolution: {integrity: sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg==}
|
||||
'@next/swc-linux-x64-musl@16.2.3':
|
||||
resolution: {integrity: sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.2.2':
|
||||
resolution: {integrity: sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g==}
|
||||
'@next/swc-win32-arm64-msvc@16.2.3':
|
||||
resolution: {integrity: sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.2.2':
|
||||
resolution: {integrity: sha512-aHaKceJgdySReT7qeck5oShucxWRiiEuwCGK8HHALe6yZga8uyFpLkPgaRw3kkF04U7ROogL/suYCNt/+CuXGA==}
|
||||
'@next/swc-win32-x64-msvc@16.2.3':
|
||||
resolution: {integrity: sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -2755,11 +2755,11 @@ packages:
|
||||
'@tauri-apps/plugin-deep-link@2.4.7':
|
||||
resolution: {integrity: sha512-K0FQlLM6BoV7Ws2xfkh+Tnwi5VZVdkI4Vw/3AGLSf0Xvu2y86AMBzd9w/SpzKhw9ai2B6ES8di/OoGDCExkOzg==}
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.6.0':
|
||||
resolution: {integrity: sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==}
|
||||
'@tauri-apps/plugin-dialog@2.7.0':
|
||||
resolution: {integrity: sha512-4nS/hfGMGCXiAS3LtVjH9AgsSAPJeG/7R+q8agTFqytjnMa4Zq95Bq8WzVDkckpanX+yyRHXnRtrKXkANKDHvw==}
|
||||
|
||||
'@tauri-apps/plugin-fs@2.4.5':
|
||||
resolution: {integrity: sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA==}
|
||||
'@tauri-apps/plugin-fs@2.5.0':
|
||||
resolution: {integrity: sha512-c83kbz61AK+rKjhS+je9+stIO27nXj7p9cqeg36TwkIUtxpCFTttlHHtqon6h6FN54cXjyAjlMPOJcW3mwE5XQ==}
|
||||
|
||||
'@tauri-apps/plugin-log@2.8.0':
|
||||
resolution: {integrity: sha512-a+7rOq3MJwpTOLLKbL8d0qGZ85hgHw5pNOWusA9o3cf7cEgtYHiGY/+O8fj8MvywQIGqFv0da2bYQDlrqLE7rw==}
|
||||
@@ -3262,8 +3262,8 @@ packages:
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
baseline-browser-mapping@2.10.13:
|
||||
resolution: {integrity: sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==}
|
||||
baseline-browser-mapping@2.10.17:
|
||||
resolution: {integrity: sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -3340,8 +3340,8 @@ packages:
|
||||
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
caniuse-lite@1.0.30001784:
|
||||
resolution: {integrity: sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==}
|
||||
caniuse-lite@1.0.30001787:
|
||||
resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
@@ -4587,8 +4587,8 @@ packages:
|
||||
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
||||
|
||||
next@16.2.2:
|
||||
resolution: {integrity: sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A==}
|
||||
next@16.2.3:
|
||||
resolution: {integrity: sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -4748,6 +4748,10 @@ packages:
|
||||
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postcss@8.5.9:
|
||||
resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
pretty-format@30.3.0:
|
||||
resolution: {integrity: sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==}
|
||||
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
@@ -5220,8 +5224,8 @@ packages:
|
||||
resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
tinyglobby@0.2.16:
|
||||
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tmpl@1.0.5:
|
||||
@@ -6317,7 +6321,7 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.9.1':
|
||||
'@emnapi/runtime@1.9.2':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
@@ -6507,7 +6511,7 @@ snapshots:
|
||||
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
dependencies:
|
||||
'@emnapi/runtime': 1.9.1
|
||||
'@emnapi/runtime': 1.9.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-arm64@0.34.5':
|
||||
@@ -6889,7 +6893,7 @@ snapshots:
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.9.1
|
||||
'@emnapi/runtime': 1.9.1
|
||||
'@emnapi/runtime': 1.9.2
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
@@ -6995,30 +6999,30 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@nestjs/platform-express': 11.1.18(@nestjs/common@11.1.18(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)
|
||||
|
||||
'@next/env@16.2.2': {}
|
||||
'@next/env@16.2.3': {}
|
||||
|
||||
'@next/swc-darwin-arm64@16.2.2':
|
||||
'@next/swc-darwin-arm64@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@16.2.2':
|
||||
'@next/swc-darwin-x64@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.2.2':
|
||||
'@next/swc-linux-arm64-gnu@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.2.2':
|
||||
'@next/swc-linux-arm64-musl@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.2.2':
|
||||
'@next/swc-linux-x64-gnu@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@16.2.2':
|
||||
'@next/swc-linux-x64-musl@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.2.2':
|
||||
'@next/swc-win32-arm64-msvc@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.2.2':
|
||||
'@next/swc-win32-x64-msvc@16.2.3':
|
||||
optional: true
|
||||
|
||||
'@noble/hashes@1.8.0': {}
|
||||
@@ -8393,11 +8397,11 @@ snapshots:
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.10.1
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.6.0':
|
||||
'@tauri-apps/plugin-dialog@2.7.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.10.1
|
||||
|
||||
'@tauri-apps/plugin-fs@2.4.5':
|
||||
'@tauri-apps/plugin-fs@2.5.0':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.10.1
|
||||
|
||||
@@ -8915,7 +8919,7 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
baseline-browser-mapping@2.10.13: {}
|
||||
baseline-browser-mapping@2.10.17: {}
|
||||
|
||||
bl@4.1.0:
|
||||
dependencies:
|
||||
@@ -8958,8 +8962,8 @@ snapshots:
|
||||
|
||||
browserslist@4.28.2:
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.10.13
|
||||
caniuse-lite: 1.0.30001784
|
||||
baseline-browser-mapping: 2.10.17
|
||||
caniuse-lite: 1.0.30001787
|
||||
electron-to-chromium: 1.5.331
|
||||
node-releases: 2.0.37
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.2)
|
||||
@@ -9003,7 +9007,7 @@ snapshots:
|
||||
|
||||
camelcase@6.3.0: {}
|
||||
|
||||
caniuse-lite@1.0.30001784: {}
|
||||
caniuse-lite@1.0.30001787: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
@@ -10350,25 +10354,25 @@ snapshots:
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
next@16.2.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@next/env': 16.2.2
|
||||
'@next/env': 16.2.3
|
||||
'@swc/helpers': 0.5.15
|
||||
baseline-browser-mapping: 2.10.13
|
||||
caniuse-lite: 1.0.30001784
|
||||
baseline-browser-mapping: 2.10.17
|
||||
caniuse-lite: 1.0.30001787
|
||||
postcss: 8.4.31
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
styled-jsx: 5.1.6(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 16.2.2
|
||||
'@next/swc-darwin-x64': 16.2.2
|
||||
'@next/swc-linux-arm64-gnu': 16.2.2
|
||||
'@next/swc-linux-arm64-musl': 16.2.2
|
||||
'@next/swc-linux-x64-gnu': 16.2.2
|
||||
'@next/swc-linux-x64-musl': 16.2.2
|
||||
'@next/swc-win32-arm64-msvc': 16.2.2
|
||||
'@next/swc-win32-x64-msvc': 16.2.2
|
||||
'@next/swc-darwin-arm64': 16.2.3
|
||||
'@next/swc-darwin-x64': 16.2.3
|
||||
'@next/swc-linux-arm64-gnu': 16.2.3
|
||||
'@next/swc-linux-arm64-musl': 16.2.3
|
||||
'@next/swc-linux-x64-gnu': 16.2.3
|
||||
'@next/swc-linux-x64-musl': 16.2.3
|
||||
'@next/swc-win32-arm64-msvc': 16.2.3
|
||||
'@next/swc-win32-x64-msvc': 16.2.3
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@@ -10499,6 +10503,12 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
postcss@8.5.9:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
pretty-format@30.3.0:
|
||||
dependencies:
|
||||
'@jest/schemas': 30.0.5
|
||||
@@ -11066,7 +11076,7 @@ snapshots:
|
||||
|
||||
tinyexec@1.0.4: {}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
tinyglobby@0.2.16:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
@@ -11288,9 +11298,9 @@ snapshots:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
rollup: 4.60.1
|
||||
tinyglobby: 0.2.15
|
||||
tinyglobby: 0.2.16
|
||||
optionalDependencies:
|
||||
'@types/node': 25.5.2
|
||||
fsevents: 2.3.3
|
||||
|
||||
Generated
+161
-69
@@ -14,7 +14,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"crypto-common 0.1.7",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
@@ -25,10 +25,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cipher 0.4.4",
|
||||
"cpufeatures 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8"
|
||||
dependencies = [
|
||||
"cipher 0.5.1",
|
||||
"cpubits",
|
||||
"cpufeatures 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.10.3"
|
||||
@@ -36,8 +47,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"aes 0.8.4",
|
||||
"cipher 0.4.4",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
@@ -158,7 +169,7 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -169,7 +180,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -339,9 +350,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c"
|
||||
checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock",
|
||||
@@ -602,7 +613,7 @@ version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -629,12 +640,21 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
name = "block-buffer"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -911,18 +931,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
|
||||
checksum = "98db6aeaef0eeef2c1e3ce9a27b739218825dae116076352ac3777076aa22225"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
"cipher 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.59"
|
||||
version = "1.2.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283"
|
||||
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -987,7 +1007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cipher 0.4.4",
|
||||
"cpufeatures 0.2.17",
|
||||
]
|
||||
|
||||
@@ -1010,7 +1030,7 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"chacha20 0.9.1",
|
||||
"cipher",
|
||||
"cipher 0.4.4",
|
||||
"poly1305",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -1045,11 +1065,21 @@ version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
"crypto-common 0.1.7",
|
||||
"inout 0.1.4",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea"
|
||||
dependencies = [
|
||||
"crypto-common 0.2.1",
|
||||
"inout 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.0"
|
||||
@@ -1121,6 +1151,12 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
@@ -1237,6 +1273,12 @@ dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpubits"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ef0c543070d296ea414df2dd7625d1b24866ce206709d8a4a424f28377f5861"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
@@ -1330,6 +1372,15 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.29.6"
|
||||
@@ -1386,7 +1437,7 @@ version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
"cipher 0.4.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1598,11 +1649,22 @@ version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"block-buffer 0.10.4",
|
||||
"crypto-common 0.1.7",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.0",
|
||||
"const-oid",
|
||||
"crypto-common 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "6.0.0"
|
||||
@@ -1630,7 +1692,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1705,9 +1767,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "donutbrowser"
|
||||
version = "0.20.2"
|
||||
version = "0.20.4"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes 0.9.0",
|
||||
"aes-gcm",
|
||||
"argon2",
|
||||
"async-socks5",
|
||||
@@ -1745,20 +1807,19 @@ dependencies = [
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"once_cell",
|
||||
"pbkdf2",
|
||||
"playwright",
|
||||
"quick-xml 0.39.2",
|
||||
"rand 0.10.0",
|
||||
"regex-lite",
|
||||
"reqwest 0.13.2",
|
||||
"resvg",
|
||||
"ring",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"serial_test",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"sha2 0.11.0",
|
||||
"smoltcp",
|
||||
"sys-locale",
|
||||
"sysinfo",
|
||||
@@ -1790,7 +1851,7 @@ dependencies = [
|
||||
"windows 0.62.2",
|
||||
"winreg 0.56.0",
|
||||
"wiremock",
|
||||
"zip 8.5.0",
|
||||
"zip 8.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1968,7 +2029,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2030,9 +2091,9 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||
|
||||
[[package]]
|
||||
name = "fax"
|
||||
@@ -2510,9 +2571,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.14.1"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e"
|
||||
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
@@ -2800,7 +2861,7 @@ version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2876,6 +2937,15 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.9.0"
|
||||
@@ -3189,10 +3259,19 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interpolate_name"
|
||||
version = "0.2.4"
|
||||
@@ -3542,9 +3621,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.15"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08"
|
||||
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"libc",
|
||||
@@ -4397,7 +4476,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4489,7 +4568,7 @@ version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
"hmac",
|
||||
]
|
||||
|
||||
@@ -5654,7 +5733,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5681,9 +5760,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.10"
|
||||
version = "0.103.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
|
||||
checksum = "20a6af516fea4b20eccceaf166e8aa666ac996208e8a644ce3ef5aa783bc7cd4"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@@ -6123,7 +6202,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6134,7 +6213,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"digest 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6269,7 +6359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6739,7 +6829,7 @@ dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"syn 2.0.117",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.18",
|
||||
@@ -6782,9 +6872,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "2.4.7"
|
||||
version = "2.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94deb2e2e4641514ac496db2cddcfc850d6fc9d51ea17b82292a0490bd20ba5b"
|
||||
checksum = "3db49816aee496a9b200d55b55ab6ae73fd50847c79f2fabc7ee20871fa75c95"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"plist",
|
||||
@@ -6803,9 +6893,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.6.0"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b"
|
||||
checksum = "a1fa4150c95ae391946cc8b8f905ab14797427caba3a8a2f79628e956da91809"
|
||||
dependencies = [
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
@@ -6821,13 +6911,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.4.5"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804"
|
||||
checksum = "36e1ec28b79f3d0683f4507e1615c36292c0ea6716668770d4396b9b39871ed8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"glob",
|
||||
"log",
|
||||
"objc2-foundation",
|
||||
"percent-encoding",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
@@ -6923,9 +7015,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-single-instance"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc61e4822b8f74d68278e09161d3e3fdd1b14b9eb781e24edccaabf10c420e8c"
|
||||
checksum = "a33a5b7d78f0dec4406b003ea87c40bf928d801b6fd9323a556172c91d8712c1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -7046,7 +7138,7 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7219,9 +7311,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.51.0"
|
||||
version = "1.51.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd"
|
||||
checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@@ -7627,7 +7719,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"tempfile",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7737,7 +7829,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"crypto-common 0.1.7",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
@@ -8221,7 +8313,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8750,7 +8842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8900,7 +8992,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"raw-window-handle",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"soup3",
|
||||
"tao-macros",
|
||||
"thiserror 2.0.18",
|
||||
@@ -9171,7 +9263,7 @@ version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes 0.8.4",
|
||||
"arbitrary",
|
||||
"bzip2",
|
||||
"constant_time_eq 0.3.1",
|
||||
@@ -9197,9 +9289,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "8.5.0"
|
||||
version = "8.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2726508a48f38dceb22b35ecbbd2430efe34ff05c62bd3285f965d7911b33464"
|
||||
checksum = "dcab981e19633ebcf0b001ddd37dd802996098bc1864f90b7c5d970ce76c1d59"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "donutbrowser"
|
||||
version = "0.20.2"
|
||||
version = "0.20.4"
|
||||
description = "Simple Yet Powerful Anti-Detect Browser"
|
||||
authors = ["zhom@github"]
|
||||
edition = "2021"
|
||||
@@ -81,11 +81,10 @@ utoipa = { version = "5", features = ["axum_extras", "chrono"] }
|
||||
utoipa-axum = "0.2"
|
||||
argon2 = "0.5"
|
||||
aes-gcm = "0.10"
|
||||
aes = "0.8"
|
||||
cbc = "0.1"
|
||||
pbkdf2 = "0.12"
|
||||
sha1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
aes = "0.9"
|
||||
cbc = "0.2"
|
||||
ring = "0.17"
|
||||
sha2 = "0.11"
|
||||
hyper = { version = "1.8", features = ["full"] }
|
||||
hyper-util = { version = "0.1", features = ["full"] }
|
||||
http-body-util = "0.1"
|
||||
|
||||
@@ -12,8 +12,10 @@ use tauri::AppHandle;
|
||||
/// so no encryption path is needed here — Chromium reads plaintext when
|
||||
/// `encrypted_value` is empty, regardless of what other cookies store.
|
||||
pub mod chrome_decrypt {
|
||||
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
|
||||
use aes::cipher::{block_padding::Pkcs7, BlockModeDecrypt, KeyIvInit};
|
||||
use ring::pbkdf2;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::num::NonZeroU32;
|
||||
use std::path::Path;
|
||||
|
||||
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
|
||||
@@ -35,7 +37,16 @@ pub mod chrome_decrypt {
|
||||
|
||||
fn derive_key(password: &[u8]) -> [u8; KEY_LEN] {
|
||||
let mut key = [0u8; KEY_LEN];
|
||||
pbkdf2::pbkdf2_hmac::<sha1::Sha1>(password, SALT, PBKDF2_ITERATIONS, &mut key);
|
||||
// Using ring::pbkdf2 instead of the `pbkdf2` crate to avoid digest
|
||||
// version conflicts between sha1 0.11 (digest 0.11) and pbkdf2 0.12
|
||||
// (digest 0.10). ring's implementation is self-contained.
|
||||
pbkdf2::derive(
|
||||
pbkdf2::PBKDF2_HMAC_SHA1,
|
||||
NonZeroU32::new(PBKDF2_ITERATIONS).expect("iterations must be non-zero"),
|
||||
SALT,
|
||||
password,
|
||||
&mut key,
|
||||
);
|
||||
key
|
||||
}
|
||||
|
||||
@@ -88,7 +99,7 @@ pub mod chrome_decrypt {
|
||||
|
||||
let mut buf = ciphertext.to_vec();
|
||||
let decrypted = Aes128CbcDec::new(key.into(), &IV.into())
|
||||
.decrypt_padded_mut::<Pkcs7>(&mut buf)
|
||||
.decrypt_padded::<Pkcs7>(&mut buf)
|
||||
.ok()?;
|
||||
|
||||
// Strip the SHA-256(host_key) integrity prefix if present. Older cookies
|
||||
|
||||
@@ -716,29 +716,22 @@ impl SyncScheduler {
|
||||
match entity_type.as_str() {
|
||||
"profile" => {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
let profile_to_delete = {
|
||||
let has_profile = {
|
||||
if let Ok(profiles) = profile_manager.list_profiles() {
|
||||
let profile_uuid = uuid::Uuid::parse_str(&entity_id).ok();
|
||||
profile_uuid.and_then(|uuid| profiles.into_iter().find(|p| p.id == uuid))
|
||||
profile_uuid.is_some_and(|uuid| profiles.iter().any(|p| p.id == uuid))
|
||||
} else {
|
||||
None
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(mut profile) = profile_to_delete {
|
||||
if has_profile {
|
||||
log::info!(
|
||||
"Profile {} was deleted remotely, disabling sync locally",
|
||||
"Profile {} was deleted remotely, deleting locally",
|
||||
entity_id
|
||||
);
|
||||
profile.sync_mode = crate::profile::types::SyncMode::Disabled;
|
||||
if let Err(e) = profile_manager.save_profile(&profile) {
|
||||
log::warn!("Failed to disable sync for profile {}: {}", entity_id, e);
|
||||
} else {
|
||||
log::info!(
|
||||
"Profile {} sync disabled due to remote tombstone (local copy kept)",
|
||||
entity_id
|
||||
);
|
||||
let _ = events::emit("profiles-changed", ());
|
||||
if let Err(e) = profile_manager.delete_profile_local_only(&entity_id) {
|
||||
log::warn!("Failed to delete tombstoned profile {}: {}", entity_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,58 +240,77 @@ impl WireGuardSocks5Server {
|
||||
socket: &UdpSocket,
|
||||
peer_addr: SocketAddr,
|
||||
) -> Result<(), VpnError> {
|
||||
let mut dst = vec![0u8; 2048];
|
||||
let result = tunn.format_handshake_initiation(&mut dst, false);
|
||||
|
||||
match result {
|
||||
TunnResult::WriteToNetwork(packet) => {
|
||||
socket
|
||||
.send_to(packet, peer_addr)
|
||||
.map_err(|e| VpnError::Connection(format!("Failed to send handshake: {e}")))?;
|
||||
}
|
||||
TunnResult::Err(e) => {
|
||||
return Err(VpnError::Tunnel(format!(
|
||||
"Handshake initiation failed: {e:?}"
|
||||
)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
socket
|
||||
.set_read_timeout(Some(std::time::Duration::from_secs(10)))
|
||||
.set_read_timeout(Some(std::time::Duration::from_secs(5)))
|
||||
.map_err(|e| VpnError::Connection(format!("Failed to set timeout: {e}")))?;
|
||||
|
||||
let mut recv_buf = vec![0u8; 2048];
|
||||
match socket.recv_from(&mut recv_buf) {
|
||||
Ok((len, _)) => {
|
||||
let result = tunn.decapsulate(None, &recv_buf[..len], &mut dst);
|
||||
match result {
|
||||
TunnResult::WriteToNetwork(response) => {
|
||||
socket
|
||||
.send_to(response, peer_addr)
|
||||
.map_err(|e| VpnError::Connection(format!("Failed to send response: {e}")))?;
|
||||
}
|
||||
TunnResult::Done => {}
|
||||
TunnResult::Err(e) => {
|
||||
return Err(VpnError::Tunnel(format!(
|
||||
"Handshake response failed: {e:?}"
|
||||
)));
|
||||
}
|
||||
_ => {}
|
||||
// WireGuard handshakes use UDP which can silently lose packets, especially
|
||||
// through Docker port-forwarding layers. Retry the handshake initiation up
|
||||
// to 5 times (25s total) before giving up — the protocol is designed for
|
||||
// retransmission and peers handle duplicate initiations correctly.
|
||||
let max_attempts = 5;
|
||||
let mut last_error = String::from("no handshake attempt completed");
|
||||
|
||||
for attempt in 1..=max_attempts {
|
||||
let mut dst = vec![0u8; 2048];
|
||||
let result = tunn.format_handshake_initiation(&mut dst, false);
|
||||
|
||||
match result {
|
||||
TunnResult::WriteToNetwork(packet) => {
|
||||
socket
|
||||
.send_to(packet, peer_addr)
|
||||
.map_err(|e| VpnError::Connection(format!("Failed to send handshake: {e}")))?;
|
||||
}
|
||||
TunnResult::Err(e) => {
|
||||
return Err(VpnError::Tunnel(format!(
|
||||
"Handshake initiation failed: {e:?}"
|
||||
)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(VpnError::Connection(format!(
|
||||
"Handshake timeout or error: {e}"
|
||||
)));
|
||||
|
||||
let mut recv_buf = vec![0u8; 2048];
|
||||
match socket.recv_from(&mut recv_buf) {
|
||||
Ok((len, _)) => {
|
||||
let result = tunn.decapsulate(None, &recv_buf[..len], &mut dst);
|
||||
match result {
|
||||
TunnResult::WriteToNetwork(response) => {
|
||||
socket
|
||||
.send_to(response, peer_addr)
|
||||
.map_err(|e| VpnError::Connection(format!("Failed to send response: {e}")))?;
|
||||
}
|
||||
TunnResult::Done => {}
|
||||
TunnResult::Err(e) => {
|
||||
last_error = format!("handshake response error: {e:?}");
|
||||
log::warn!(
|
||||
"[vpn-worker] Handshake attempt {attempt}/{max_attempts} failed: {last_error}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
socket
|
||||
.set_read_timeout(None)
|
||||
.map_err(|e| VpnError::Connection(format!("Failed to clear timeout: {e}")))?;
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) if attempt < max_attempts => {
|
||||
log::warn!(
|
||||
"[vpn-worker] Handshake attempt {attempt}/{max_attempts} timed out: {e}, retrying"
|
||||
);
|
||||
last_error = format!("timeout: {e}");
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
last_error = format!("timeout: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
socket
|
||||
.set_read_timeout(None)
|
||||
.map_err(|e| VpnError::Connection(format!("Failed to clear timeout: {e}")))?;
|
||||
|
||||
Ok(())
|
||||
Err(VpnError::Connection(format!(
|
||||
"Handshake failed after {max_attempts} attempts: {last_error}"
|
||||
)))
|
||||
}
|
||||
|
||||
pub async fn run(self, config_id: String) -> Result<(), VpnError> {
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::vpn_worker_storage::{
|
||||
use std::process::Stdio;
|
||||
|
||||
const VPN_WORKER_POLL_INTERVAL_MS: u64 = 100;
|
||||
const VPN_WORKER_STARTUP_TIMEOUT_MS: u64 = 10_000;
|
||||
const VPN_WORKER_STARTUP_TIMEOUT_MS: u64 = 30_000;
|
||||
const OPENVPN_WORKER_STARTUP_TIMEOUT_MS: u64 = 100_000;
|
||||
|
||||
async fn vpn_worker_accepting_connections(config: &VpnWorkerConfig) -> bool {
|
||||
|
||||
@@ -86,7 +86,18 @@ impl WayfernManager {
|
||||
inner: Arc::new(AsyncMutex::new(WayfernManagerInner {
|
||||
instances: HashMap::new(),
|
||||
})),
|
||||
http_client: Client::new(),
|
||||
// Every request this client makes goes to a local `http://127.0.0.1:<port>`
|
||||
// endpoint that Wayfern is still bringing up. Without a per-request timeout,
|
||||
// a single hanging connect or a stuck HTTP response will block
|
||||
// `wait_for_cdp_ready` indefinitely — its 120-attempt poll loop depends on
|
||||
// each request returning fast. A 2-second per-request timeout turns that
|
||||
// into a worst-case ~60-second bounded wait, and `generate_fingerprint_config`
|
||||
// can then return a real error instead of hanging the profile-creation UI
|
||||
// forever.
|
||||
http_client: Client::builder()
|
||||
.timeout(Duration::from_secs(2))
|
||||
.build()
|
||||
.expect("Failed to build reqwest client for wayfern_manager"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,19 +152,29 @@ impl WayfernManager {
|
||||
let max_attempts = 120;
|
||||
let delay = Duration::from_millis(500);
|
||||
|
||||
let mut last_error: Option<String> = None;
|
||||
for attempt in 0..max_attempts {
|
||||
match self.http_client.get(&url).send().await {
|
||||
Ok(resp) if resp.status().is_success() => {
|
||||
log::info!("CDP ready on port {port} after {attempt} attempts");
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
Ok(resp) => {
|
||||
last_error = Some(format!("HTTP {} from {url}", resp.status()));
|
||||
tokio::time::sleep(delay).await;
|
||||
}
|
||||
Err(e) => {
|
||||
last_error = Some(format!("request failed: {e}"));
|
||||
tokio::time::sleep(delay).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!("CDP not ready after {max_attempts} attempts on port {port}").into())
|
||||
let detail = last_error.unwrap_or_else(|| "no attempts completed".to_string());
|
||||
// Log at error level so we can diagnose Windows/AV/firewall-induced CDP hangs
|
||||
// in customer reports without needing them to reproduce in the moment.
|
||||
log::error!("CDP not ready after {max_attempts} attempts on port {port}: {detail}");
|
||||
Err(format!("CDP not ready after {max_attempts} attempts on port {port}: {detail}").into())
|
||||
}
|
||||
|
||||
async fn get_cdp_targets(
|
||||
@@ -270,6 +291,9 @@ impl WayfernManager {
|
||||
};
|
||||
|
||||
if let Err(e) = self.wait_for_cdp_ready(port).await {
|
||||
log::error!(
|
||||
"Fingerprint-generation Wayfern (headless, pid={child_id:?}) never became CDP-ready: {e}"
|
||||
);
|
||||
cleanup().await;
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Donut",
|
||||
"version": "0.20.2",
|
||||
"version": "0.20.4",
|
||||
"identifier": "com.donutbrowser",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm copy-proxy-binary && pnpm dev",
|
||||
|
||||
@@ -90,8 +90,40 @@ pub async fn start_wireguard_server() -> Result<WireGuardTestConfig, String> {
|
||||
));
|
||||
}
|
||||
|
||||
// Wait for container to be ready and generate configs
|
||||
sleep(Duration::from_secs(10)).await;
|
||||
// Wait for container to generate configs and bring up the WireGuard interface.
|
||||
// A fixed sleep is flaky — on busy machines the interface takes longer. Instead
|
||||
// we poll `wg show` inside the container until it reports an active interface,
|
||||
// with a generous upper bound.
|
||||
let wg_ready_deadline = tokio::time::Instant::now() + Duration::from_secs(45);
|
||||
loop {
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
|
||||
// Check if peer config file has been generated
|
||||
let config_check = Command::new("docker")
|
||||
.args(["exec", WG_CONTAINER, "cat", "/config/peer1/peer1.conf"])
|
||||
.output();
|
||||
let config_exists = config_check
|
||||
.as_ref()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false);
|
||||
|
||||
// Check if WireGuard interface is actually up and listening
|
||||
let wg_check = Command::new("docker")
|
||||
.args(["exec", WG_CONTAINER, "wg", "show"])
|
||||
.output();
|
||||
let wg_up = wg_check
|
||||
.as_ref()
|
||||
.map(|o| o.status.success() && String::from_utf8_lossy(&o.stdout).contains("listening port"))
|
||||
.unwrap_or(false);
|
||||
|
||||
if config_exists && wg_up {
|
||||
break;
|
||||
}
|
||||
|
||||
if tokio::time::Instant::now() >= wg_ready_deadline {
|
||||
return Err("WireGuard container did not become ready within 45s".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Extract client config from container
|
||||
let config_output = Command::new("docker")
|
||||
@@ -107,7 +139,30 @@ pub async fn start_wireguard_server() -> Result<WireGuardTestConfig, String> {
|
||||
}
|
||||
|
||||
let config_str = String::from_utf8_lossy(&config_output.stdout).to_string();
|
||||
parse_wireguard_test_config(&config_str)
|
||||
let mut config = parse_wireguard_test_config(&config_str)?;
|
||||
|
||||
// Start a lightweight HTTP server inside the container on the WireGuard
|
||||
// interface so tests can verify traffic flows through the tunnel without
|
||||
// depending on internet access (Docker Desktop for Mac can't reliably NAT
|
||||
// WireGuard tunnel traffic to the internet). The linuxserver/wireguard
|
||||
// image doesn't have python3 or busybox httpd, but it has nc (netcat).
|
||||
let _ = Command::new("docker")
|
||||
.args([
|
||||
"exec",
|
||||
"-d",
|
||||
WG_CONTAINER,
|
||||
"sh",
|
||||
"-c",
|
||||
r#"while true; do printf "HTTP/1.1 200 OK\r\nContent-Length: 13\r\nConnection: close\r\n\r\nWG-TUNNEL-OK\n" | nc -l -p 8080 2>/dev/null; done"#,
|
||||
])
|
||||
.output();
|
||||
// Give the nc loop a moment to start accepting
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// Extract the server's tunnel IP (first octet group from INTERNAL_SUBNET + .1)
|
||||
config.server_tunnel_ip = "10.64.0.1".to_string();
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Start an OpenVPN test server and return client config
|
||||
@@ -282,6 +337,10 @@ pub struct WireGuardTestConfig {
|
||||
pub peer_endpoint: String,
|
||||
pub allowed_ips: Vec<String>,
|
||||
pub preshared_key: Option<String>,
|
||||
/// IP of the WireGuard server on the tunnel interface (e.g. 10.64.0.1).
|
||||
/// Tests use this to reach an HTTP server inside the container without
|
||||
/// needing internet access from Docker.
|
||||
pub server_tunnel_ip: String,
|
||||
}
|
||||
|
||||
/// OpenVPN test configuration
|
||||
@@ -355,6 +414,7 @@ fn parse_wireguard_test_config(content: &str) -> Result<WireGuardTestConfig, Str
|
||||
peer_endpoint,
|
||||
allowed_ips,
|
||||
preshared_key,
|
||||
server_tunnel_ip: String::new(), // filled in by caller
|
||||
})
|
||||
}
|
||||
|
||||
@@ -382,6 +442,8 @@ fn get_ci_wireguard_config(host: &str, port: &str) -> Result<WireGuardTestConfig
|
||||
peer_endpoint: format!("{host}:{port}"),
|
||||
allowed_ips: vec!["0.0.0.0/0".to_string()],
|
||||
preshared_key: std::env::var("VPN_TEST_WG_PRESHARED_KEY").ok(),
|
||||
server_tunnel_ip: std::env::var("VPN_TEST_WG_SERVER_IP")
|
||||
.unwrap_or_else(|_| "10.0.0.1".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -487,7 +487,6 @@ impl Drop for TestEnvGuard {
|
||||
struct ProxyProcess {
|
||||
id: String,
|
||||
local_port: u16,
|
||||
local_url: String,
|
||||
}
|
||||
|
||||
async fn ensure_donut_proxy_binary() -> Result<PathBuf, Box<dyn std::error::Error + Send + Sync>> {
|
||||
@@ -664,10 +663,6 @@ async fn start_proxy_with_upstream(
|
||||
Ok(ProxyProcess {
|
||||
id: config["id"].as_str().ok_or("Missing proxy id")?.to_string(),
|
||||
local_port: config["localPort"].as_u64().ok_or("Missing local port")? as u16,
|
||||
local_url: config["localUrl"]
|
||||
.as_str()
|
||||
.ok_or("Missing local URL")?
|
||||
.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -696,28 +691,23 @@ async fn raw_http_request_via_proxy(
|
||||
url: &str,
|
||||
host_header: &str,
|
||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut stream = TcpStream::connect(("127.0.0.1", local_port)).await?;
|
||||
let mut stream = tokio::time::timeout(
|
||||
Duration::from_secs(20),
|
||||
TcpStream::connect(("127.0.0.1", local_port)),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| "proxy TCP connect timed out after 20s")??;
|
||||
|
||||
let request = format!("GET {url} HTTP/1.1\r\nHost: {host_header}\r\nConnection: close\r\n\r\n");
|
||||
stream.write_all(request.as_bytes()).await?;
|
||||
|
||||
let mut response = Vec::new();
|
||||
stream.read_to_end(&mut response).await?;
|
||||
tokio::time::timeout(Duration::from_secs(20), stream.read_to_end(&mut response))
|
||||
.await
|
||||
.map_err(|_| "proxy HTTP response timed out after 20s")??;
|
||||
Ok(String::from_utf8_lossy(&response).to_string())
|
||||
}
|
||||
|
||||
async fn https_get_via_proxy(
|
||||
local_proxy_url: &str,
|
||||
url: &str,
|
||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(20))
|
||||
.no_proxy()
|
||||
.proxy(reqwest::Proxy::all(local_proxy_url)?)
|
||||
.build()?;
|
||||
|
||||
Ok(client.get(url).send().await?.text().await?)
|
||||
}
|
||||
|
||||
async fn cleanup_runtime() {
|
||||
let _ = donutbrowser_lib::proxy_runner::stop_all_proxy_processes().await;
|
||||
let _ = donutbrowser_lib::vpn_worker_runner::stop_all_vpn_workers().await;
|
||||
@@ -744,6 +734,7 @@ async fn wait_for_file(
|
||||
async fn run_proxy_feature_suite(
|
||||
binary_path: &PathBuf,
|
||||
vpn_id: &str,
|
||||
server_tunnel_ip: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let vpn_worker = donutbrowser_lib::vpn_worker_runner::start_vpn_worker(vpn_id)
|
||||
.await
|
||||
@@ -759,20 +750,20 @@ async fn run_proxy_feature_suite(
|
||||
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// Test HTTP traffic through the tunnel to the internal HTTP server running
|
||||
// inside the WireGuard container. This avoids depending on internet access
|
||||
// from Docker (macOS Docker Desktop can't reliably NAT WireGuard tunnel
|
||||
// traffic through to the internet).
|
||||
let internal_url = format!("http://{}:8080/", server_tunnel_ip);
|
||||
let internal_host = format!("{}:8080", server_tunnel_ip);
|
||||
let http_response =
|
||||
raw_http_request_via_proxy(proxy.local_port, "http://example.com/", "example.com").await?;
|
||||
raw_http_request_via_proxy(proxy.local_port, &internal_url, &internal_host).await?;
|
||||
assert!(
|
||||
http_response.contains("Example Domain"),
|
||||
"HTTP traffic through donut-proxy+VPN should succeed, got: {}",
|
||||
http_response.contains("WG-TUNNEL-OK"),
|
||||
"HTTP traffic through donut-proxy+VPN tunnel should succeed, got: {}",
|
||||
&http_response[..http_response.len().min(300)]
|
||||
);
|
||||
|
||||
let https_body = https_get_via_proxy(&proxy.local_url, "https://example.com/").await?;
|
||||
assert!(
|
||||
https_body.contains("Example Domain"),
|
||||
"HTTPS traffic through donut-proxy+VPN should succeed"
|
||||
);
|
||||
|
||||
let stats_file = donutbrowser_lib::app_dirs::cache_dir()
|
||||
.join("traffic_stats")
|
||||
.join(format!("{}.json", profile_id));
|
||||
@@ -792,14 +783,16 @@ async fn run_proxy_feature_suite(
|
||||
.as_object()
|
||||
.ok_or("Traffic stats are missing per-domain data")?;
|
||||
assert!(
|
||||
domains.contains_key("example.com"),
|
||||
"Traffic stats should include example.com domain activity"
|
||||
domains.contains_key(server_tunnel_ip),
|
||||
"Traffic stats should include tunnel server IP activity, got: {:?}",
|
||||
domains.keys().collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
stop_proxy(binary_path, &proxy.id).await?;
|
||||
|
||||
// DNS blocklist test: blocklist the tunnel server IP so it gets rejected
|
||||
let blocklist_file = tempfile::NamedTempFile::new()?;
|
||||
std::fs::write(blocklist_file.path(), "example.com\n")?;
|
||||
std::fs::write(blocklist_file.path(), format!("{server_tunnel_ip}\n"))?;
|
||||
let blocked_proxy = start_proxy_with_upstream(
|
||||
binary_path,
|
||||
&vpn_upstream,
|
||||
@@ -808,12 +801,8 @@ async fn run_proxy_feature_suite(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let blocked_response = raw_http_request_via_proxy(
|
||||
blocked_proxy.local_port,
|
||||
"http://example.com/",
|
||||
"example.com",
|
||||
)
|
||||
.await?;
|
||||
let blocked_response =
|
||||
raw_http_request_via_proxy(blocked_proxy.local_port, &internal_url, &internal_host).await?;
|
||||
assert!(
|
||||
blocked_response.contains("403") || blocked_response.contains("Blocked by DNS blocklist"),
|
||||
"DNS blocklist should be enforced before forwarding to the VPN upstream"
|
||||
@@ -875,8 +864,8 @@ async fn run_proxy_feature_suite(
|
||||
async fn test_wireguard_traffic_flows_through_donut_proxy(
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let _env = TestEnvGuard::new()?;
|
||||
cleanup_runtime().await;
|
||||
|
||||
cleanup_runtime().await;
|
||||
if !test_harness::is_docker_available() {
|
||||
eprintln!("skipping WireGuard e2e test because Docker is unavailable");
|
||||
return Ok(());
|
||||
@@ -901,8 +890,10 @@ async fn test_wireguard_traffic_flows_through_donut_proxy(
|
||||
storage.save_config(&vpn_config)?;
|
||||
}
|
||||
|
||||
let result = run_proxy_feature_suite(&binary_path, &vpn_config.id).await;
|
||||
let result =
|
||||
run_proxy_feature_suite(&binary_path, &vpn_config.id, &wg_config.server_tunnel_ip).await;
|
||||
cleanup_runtime().await;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
@@ -952,7 +943,9 @@ async fn test_openvpn_traffic_flows_through_donut_proxy(
|
||||
storage.save_config(&vpn_config)?;
|
||||
}
|
||||
|
||||
let result = run_proxy_feature_suite(&binary_path, &vpn_config.id).await;
|
||||
// OpenVPN test uses the server's tunnel IP for internal-only traffic.
|
||||
// The OpenVPN server's subnet is 10.9.0.0/24, server at 10.9.0.1.
|
||||
let result = run_proxy_feature_suite(&binary_path, &vpn_config.id, "10.9.0.1").await;
|
||||
cleanup_runtime().await;
|
||||
result
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { LoadingButton } from "@/components/loading-button";
|
||||
import {
|
||||
@@ -31,6 +32,7 @@ export function DeleteGroupDialog({
|
||||
group,
|
||||
onGroupDeleted,
|
||||
}: DeleteGroupDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [associatedProfiles, setAssociatedProfiles] = useState<
|
||||
BrowserProfile[]
|
||||
>([]);
|
||||
@@ -155,7 +157,7 @@ export function DeleteGroupDialog({
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="move" id="move" />
|
||||
<Label htmlFor="move" className="text-sm">
|
||||
Move profiles to Default group
|
||||
{t("groups.moveToDefault")}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GoPlus } from "react-icons/go";
|
||||
import { toast } from "sonner";
|
||||
import { CreateGroupDialog } from "@/components/create-group-dialog";
|
||||
@@ -40,6 +41,7 @@ export function GroupAssignmentDialog({
|
||||
onAssignmentComplete,
|
||||
profiles = [],
|
||||
}: GroupAssignmentDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [groups, setGroups] = useState<ProfileGroup[]>([]);
|
||||
const [selectedGroupId, setSelectedGroupId] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -72,10 +74,13 @@ export function GroupAssignmentDialog({
|
||||
|
||||
const groupName = selectedGroupId
|
||||
? groups.find((g) => g.id === selectedGroupId)?.name || "Unknown Group"
|
||||
: "Default";
|
||||
: t("groups.defaultGroup");
|
||||
|
||||
toast.success(
|
||||
`Successfully assigned ${selectedProfiles.length} profile(s) to ${groupName}`,
|
||||
t("groups.assignSuccess", {
|
||||
count: selectedProfiles.length,
|
||||
group: groupName,
|
||||
}),
|
||||
);
|
||||
onAssignmentComplete();
|
||||
onClose();
|
||||
@@ -96,6 +101,7 @@ export function GroupAssignmentDialog({
|
||||
groups,
|
||||
onAssignmentComplete,
|
||||
onClose,
|
||||
t,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -166,7 +172,9 @@ export function GroupAssignmentDialog({
|
||||
<SelectValue placeholder="Select a group" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="default">Default (No Group)</SelectItem>
|
||||
<SelectItem value="default">
|
||||
{t("groups.defaultGroupNoGroup")}
|
||||
</SelectItem>
|
||||
{groups.map((group) => (
|
||||
<SelectItem key={group.id} value={group.id}>
|
||||
{group.name}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import type { GroupWithCount } from "@/types";
|
||||
|
||||
@@ -18,6 +19,7 @@ export function GroupBadges({
|
||||
groups,
|
||||
isLoading,
|
||||
}: GroupBadgesProps) {
|
||||
const { t } = useTranslation();
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [showLeftFade, setShowLeftFade] = useState(false);
|
||||
const [showRightFade, setShowRightFade] = useState(false);
|
||||
@@ -181,7 +183,9 @@ export function GroupBadges({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span>{group.name}</span>
|
||||
<span>
|
||||
{group.id === "default" ? t("groups.defaultGroup") : group.name}
|
||||
</span>
|
||||
<span className="bg-background/20 text-xs px-1.5 py-0.5 rounded-sm">
|
||||
{group.count}
|
||||
</span>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GoPlus } from "react-icons/go";
|
||||
import { LuPencil, LuTrash2 } from "react-icons/lu";
|
||||
import { CreateGroupDialog } from "@/components/create-group-dialog";
|
||||
@@ -90,6 +91,7 @@ export function GroupManagementDialog({
|
||||
onClose,
|
||||
onGroupManagementComplete,
|
||||
}: GroupManagementDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [groups, setGroups] = useState<GroupWithCount[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -233,10 +235,9 @@ export function GroupManagementDialog({
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-2xl max-h-[90vh] flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Manage Profile Groups</DialogTitle>
|
||||
<DialogTitle>{t("groups.management")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create, edit, and delete profile groups. Profiles without a group
|
||||
will appear in the "Default" group.
|
||||
{t("groups.noGroupDescription")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -265,12 +266,11 @@ export function GroupManagementDialog({
|
||||
{/* Groups list */}
|
||||
{isLoading ? (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Loading groups...
|
||||
{t("common.loading")}
|
||||
</div>
|
||||
) : groups.length === 0 ? (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
No groups created yet. Create your first group using the button
|
||||
above.
|
||||
{t("groups.noGroupsDescription")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="border rounded-md">
|
||||
|
||||
@@ -243,14 +243,73 @@ export function IntegrationsDialog({
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">Port</Label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={
|
||||
isApiStarting || apiServerPort === settings.api_port
|
||||
}
|
||||
onClick={async () => {
|
||||
const port = settings.api_port;
|
||||
if (port < 1 || port > 65535) {
|
||||
showErrorToast("Invalid port", {
|
||||
description: "Port must be between 1 and 65535",
|
||||
});
|
||||
return;
|
||||
}
|
||||
setIsApiStarting(true);
|
||||
try {
|
||||
await invoke("stop_api_server");
|
||||
const next = await invoke<AppSettings>(
|
||||
"save_app_settings",
|
||||
{ settings },
|
||||
);
|
||||
setSettings(next);
|
||||
const actualPort = await invoke<number>(
|
||||
"start_api_server",
|
||||
{ port },
|
||||
);
|
||||
setApiServerPort(actualPort);
|
||||
if (actualPort !== port) {
|
||||
showErrorToast(`Port ${port} is already in use`, {
|
||||
description: `Server started on fallback port ${actualPort}`,
|
||||
});
|
||||
} else {
|
||||
showSuccessToast(
|
||||
`API server running on port ${actualPort}`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
showErrorToast("Failed to start API server", {
|
||||
description:
|
||||
e instanceof Error
|
||||
? e.message
|
||||
: "Unknown error",
|
||||
});
|
||||
} finally {
|
||||
setIsApiStarting(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("common.buttons.save")}
|
||||
</Button>
|
||||
<Input
|
||||
value={apiServerPort ?? settings.api_port}
|
||||
readOnly
|
||||
type="number"
|
||||
value={settings.api_port}
|
||||
onChange={(e) => {
|
||||
const val = Number.parseInt(e.target.value, 10);
|
||||
if (!Number.isNaN(val)) {
|
||||
setSettings({ ...settings, api_port: val });
|
||||
}
|
||||
}}
|
||||
className="w-24 font-mono"
|
||||
min={1}
|
||||
max={65535}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Server is running
|
||||
</span>
|
||||
{apiServerPort && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t("common.status.running")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -323,6 +323,11 @@
|
||||
"add": "Add Group",
|
||||
"edit": "Edit Group",
|
||||
"delete": "Delete Group",
|
||||
"defaultGroup": "Default",
|
||||
"defaultGroupNoGroup": "Default (No Group)",
|
||||
"moveToDefault": "Move profiles to Default group",
|
||||
"noGroupDescription": "Profiles without a group will appear in the \"Default\" group.",
|
||||
"assignSuccess": "Successfully assigned {{count}} profile(s) to {{group}}",
|
||||
"noGroups": "No groups created",
|
||||
"noGroupsDescription": "Create a group to organize your profiles.",
|
||||
"form": {
|
||||
|
||||
@@ -323,6 +323,11 @@
|
||||
"add": "Agregar Grupo",
|
||||
"edit": "Editar Grupo",
|
||||
"delete": "Eliminar Grupo",
|
||||
"defaultGroup": "Predeterminado",
|
||||
"defaultGroupNoGroup": "Predeterminado (Sin Grupo)",
|
||||
"moveToDefault": "Mover perfiles al grupo Predeterminado",
|
||||
"noGroupDescription": "Los perfiles sin grupo aparecerán en el grupo \"Predeterminado\".",
|
||||
"assignSuccess": "Se asignaron {{count}} perfil(es) a {{group}} exitosamente",
|
||||
"noGroups": "No hay grupos creados",
|
||||
"noGroupsDescription": "Crea un grupo para organizar tus perfiles.",
|
||||
"form": {
|
||||
|
||||
@@ -323,6 +323,11 @@
|
||||
"add": "Ajouter un groupe",
|
||||
"edit": "Modifier le groupe",
|
||||
"delete": "Supprimer le groupe",
|
||||
"defaultGroup": "Par défaut",
|
||||
"defaultGroupNoGroup": "Par défaut (Aucun groupe)",
|
||||
"moveToDefault": "Déplacer les profils vers le groupe Par défaut",
|
||||
"noGroupDescription": "Les profils sans groupe apparaîtront dans le groupe « Par défaut ».",
|
||||
"assignSuccess": "{{count}} profil(s) assigné(s) à {{group}} avec succès",
|
||||
"noGroups": "Aucun groupe créé",
|
||||
"noGroupsDescription": "Créez un groupe pour organiser vos profils.",
|
||||
"form": {
|
||||
|
||||
@@ -323,6 +323,11 @@
|
||||
"add": "グループを追加",
|
||||
"edit": "グループを編集",
|
||||
"delete": "グループを削除",
|
||||
"defaultGroup": "デフォルト",
|
||||
"defaultGroupNoGroup": "デフォルト(グループなし)",
|
||||
"moveToDefault": "プロファイルをデフォルトグループに移動",
|
||||
"noGroupDescription": "グループに属していないプロファイルは「デフォルト」グループに表示されます。",
|
||||
"assignSuccess": "{{count}} 件のプロファイルを {{group}} に割り当てました",
|
||||
"noGroups": "グループがありません",
|
||||
"noGroupsDescription": "プロファイルを整理するためのグループを作成してください。",
|
||||
"form": {
|
||||
|
||||
@@ -323,6 +323,11 @@
|
||||
"add": "Adicionar Grupo",
|
||||
"edit": "Editar Grupo",
|
||||
"delete": "Excluir Grupo",
|
||||
"defaultGroup": "Padrão",
|
||||
"defaultGroupNoGroup": "Padrão (Sem Grupo)",
|
||||
"moveToDefault": "Mover perfis para o grupo Padrão",
|
||||
"noGroupDescription": "Perfis sem grupo aparecerão no grupo \"Padrão\".",
|
||||
"assignSuccess": "{{count}} perfil(s) atribuído(s) a {{group}} com sucesso",
|
||||
"noGroups": "Nenhum grupo criado",
|
||||
"noGroupsDescription": "Crie um grupo para organizar seus perfis.",
|
||||
"form": {
|
||||
|
||||
@@ -323,6 +323,11 @@
|
||||
"add": "Добавить группу",
|
||||
"edit": "Редактировать группу",
|
||||
"delete": "Удалить группу",
|
||||
"defaultGroup": "По умолчанию",
|
||||
"defaultGroupNoGroup": "По умолчанию (Без группы)",
|
||||
"moveToDefault": "Переместить профили в группу по умолчанию",
|
||||
"noGroupDescription": "Профили без группы будут отображаться в группе «По умолчанию».",
|
||||
"assignSuccess": "Успешно назначено {{count}} профиль(ей) в {{group}}",
|
||||
"noGroups": "Группы не созданы",
|
||||
"noGroupsDescription": "Создайте группу для организации профилей.",
|
||||
"form": {
|
||||
|
||||
@@ -323,6 +323,11 @@
|
||||
"add": "添加分组",
|
||||
"edit": "编辑分组",
|
||||
"delete": "删除分组",
|
||||
"defaultGroup": "默认",
|
||||
"defaultGroupNoGroup": "默认(无分组)",
|
||||
"moveToDefault": "将配置文件移至默认分组",
|
||||
"noGroupDescription": "未分组的配置文件将显示在「默认」分组中。",
|
||||
"assignSuccess": "已成功将 {{count}} 个配置文件分配到 {{group}}",
|
||||
"noGroups": "暂无分组",
|
||||
"noGroupsDescription": "创建分组来组织您的配置文件。",
|
||||
"form": {
|
||||
|
||||
Reference in New Issue
Block a user