Commit Graph
105 Commits
Author SHA1 Message Date
liumangmang 854a640d48 fix: shorten New-API token names 2026-06-30 16:12:46 +08:00
liumangmang 8bff1c1551 chore: remove reasonix artifacts 2026-06-30 14:36:13 +08:00
liumangmang c5bb63cf82 feat: support upstream token refresh 2026-06-30 14:34:40 +08:00
SmartUp Developer 2d1dcb8f9f fix: round website rates to two decimals 2026-06-30 11:26:48 +08:00
liumangmang a914d3d222 feat: add Nox-API upstream support 2026-06-30 11:22:38 +08:00
SmartUp Developer 9600e4ceba fix: backfill account groups before priority reorder 2026-06-03 18:39:21 +08:00
SmartUp Developer b866b387e0 feat: sync upstream keys and reorder priorities 2026-06-03 17:03:11 +08:00
liumangmang a42bcba483 Remove server remote browser support 2026-06-02 19:25:20 +08:00
liumangmang 3181a6f6cc feat: add browser extension origin save 2026-06-02 15:32:43 +08:00
liumangmang 84148f4a69 feat: support real browser auth import 2026-06-02 13:51:29 +08:00
liumangmang f4d16a4c01 fix(frontend): include cookie_count and cookie_names in AuthCaptureDialog emit 2026-06-02 10:07:32 +08:00
liumangmang 0b1daf277b fix(frontend): apply new-api-user endpoint reset to cookie_bundle capture branch 2026-06-02 09:58:18 +08:00
liumangmang b77ea21ca8 fix(frontend): support cookie_bundle candidate in AuthCaptureDialog and refreshAuth 2026-06-02 09:52:43 +08:00
liumangmang 4f9acdc99c feat(auth-capture): full cookie bundle extraction + richer refresh-auth
Problem: Meow upstream uses Cloudflare, which sets cf_clearance + session
cookies that must all be sent together. The old code only captured a single
session-named cookie via a whitelist, discarding cf_clearance entirely, and
wrote back only 'name=value' instead of the full cookie string.

Changes:

auth_capture_service.py:
  - Add _cookie_matches_hostname(): hostname suffix matching supporting
    dot-prefixed domains (.saki.lat matches api.saki.lat)
  - Add _build_cookie_bundle(): collects ALL cookies matching the current
    page's hostname, returns complete 'name1=v1; name2=v2' string
  - _curate_candidates(): new 'cookie_bundle' candidate type (type=0 in sort,
    highest priority), carries cookie_count + cookie_names in extra fields
  - extract_all(): obtain real-time page URL from session.page.url and pass
    to _curate_candidates so cookie domain filtering is accurate
  - Sort order: cookie_bundle > cookie > bearer_token/api_key > credential
  - Fix bug in original JWT dedup check (was assigning instead of checking)

custom_pages.py:
  - Add logging import + logger
  - _pick_best_candidate(): cookie preferred_auth_type now tries cookie_bundle
    first, then single cookie; bearer/api_key use existing type_map logic
  - RefreshAuthResponse: add optional 'warning' field
  - refresh_auth(): handle ctype='cookie_bundle' same as 'cookie'; always
    write full candidate.value as cookie_string (works for both types)
  - Post-write validation: attempt get_available_groups with new credentials;
    on failure, still commit (lenient mode) but set warning message explaining
    cf_clearance IP-binding as the likely cause; success logs at INFO level

Tests (test_auth_capture.py, 19 cases):
  - _cookie_matches_hostname: exact, dot-prefix subdomain, empty domain,
    different domain, evil-subdomain partial match rejection
  - _build_cookie_bundle: cf_clearance included, cross-domain excluded,
    single cookie, empty value excluded, no cookies
  - _curate_candidates: bundle ranks first, value is full string, bundle
    beats single session cookie, bearer wins when no cookies, empty case,
    cookie_count/cookie_names in extra, session fallback preserved,
    new_api_user propagation to bundle

All 46 tests pass.
2026-06-02 09:32:23 +08:00
liumangmang f17317b13c fix(priority-sync): handle missing rate data and backfill target group on re-import
P1 - Missing rate data now skips account instead of falling back to 1.0:
  In sync_account_priorities_for_upstream(), the rated list now filters
  out accounts whose upstream snapshot has no rate entry for their group_id.
  If after filtering a competitive bucket has fewer than 2 accounts with
  valid rate data, the entire bucket is silently skipped (no update_account
  call, no webhook) rather than treating missing rates as 1.0 and
  potentially triggering spurious notifications.

P2 - Re-importing an existing account now backfills imported_target_group_id:
  In the exists-is-True idempotency branch of import_upstream_keys_as_accounts(),
  if the current request supplies a target_group_id for the account's source group
  and it differs from what is stored, the field is written back and committed.
  This lets operators fix old data by simply re-running the import dialog.

Tests added:
  - test_missing_rate_skips_entire_competitive_group: all accounts in
    competitive group lack snapshot → bucket skipped, no update called
  - test_partial_missing_rate_sufficient_accounts_still_updates: 3 accounts
    in same bucket, 1 missing rate → the 2 with rates still compete normally

All 27 tests pass.
2026-06-01 19:27:35 +08:00
liumangmang e519d1804b fix(priority-sync): narrow account priority update to competitive groups only
Root cause: sync_account_priorities_for_upstream() was doing a global
priority re-rank across ALL imported accounts on a website whenever any
upstream rate changed, triggering spurious account_priority_changed
notifications for accounts in different target groups with no competition.

Fix:
- Add imported_target_group_id / imported_target_group_name to
  UpstreamGeneratedKey (nullable; old data falls back to group_id)
- Writ imported_target_group_id on account import in websites.py
- Rewrite sync_account_priorities_for_upstream():
  * bucket accounts by competition_group = imported_target_group_id or group_id
  * only process buckets with count > 1 (genuine competition)
  * each competitive bucket independently sorted by rate; priority starts at 1
  * single-account groups: completely skipped (no update_account, no notification)
  * no competitive groups at all: early return, no log, no notification
- Remove auto priority update in re-import idempotency path (was also
  incorrect; now fully delegated to sync_account_priorities_for_upstream)
- Fix Sub2ApiWebsiteClient local import in sync fn → use module-level name
  so monkeypatch works correctly in tests

Tests: rewrite test_priority_sync.py
- REMOVED: test_priority_sync_full_website_update (was asserting the buggy behavior)
- NEW: test_no_update_when_different_groups_single_account_each
- NEW: test_same_target_group_two_accounts_updated
- NEW: test_two_target_groups_independent_priority
- NEW: test_old_data_null_target_group_fallback
- NEW: test_single_account_in_mixed_website
- UPDATED: test_priority_sync_log_structure (now requires competitive group)
- KEPT: test_priority_sync_cross_upstream_group, test_import_auto_priority_by_rate

All 25 tests pass (8 priority_sync + 17 existing upstream tests).
2026-06-01 19:13:14 +08:00
liumangmang 871557e4ae feat(upstreams): add batch test-all / check-now-all endpoints
- POST /api/upstreams/test-all: batch connection test for all enabled
  upstreams (no snapshot, no webhook); updates last_status, balance
- POST /api/upstreams/check-now-all: full batch sync (snapshot, diff,
  webhook, key sync, priority sync); mirrors single check-now behavior
- Both routes are registered before /{uid} to avoid path capture
- Skips disabled upstreams (status=skipped); single failure does not
  abort subsequent upstreams (serial execution)
- Returns UpstreamBatchActionResponse with per-item detail and summary

Refactor: extract _test_upstream_core(db, u) and _check_now_core(db, u)
- All four routes (single + batch × 2) now share the same core helpers
- Eliminates duplicate logic and future divergence risk

Frontend:
- Add UpstreamBatchActionResponse / Item / Summary TS types
- Add upstreamsApi.testAll() and upstreamsApi.checkNowAll()
- Add '一键测试' and '一键同步' buttons in Upstreams.vue toolbar
  (order: 一键测试 → 一键同步 → 刷新 → 新增上游)
- Buttons disabled when list is empty or another batch op is running
- On completion: refresh list + ElMessageBox with per-item failure detail
2026-06-01 16:46:42 +08:00
liumangmang a949969c4d perf: remote browser CPU / zombie optimization
- Add tini (init: true in compose) to reap orphan Chromium processes
- Reduce screenshot push frequency (active 0.12→0.20s, idle 0.35→1.00s,
  deep idle 1.00→5.00s, backoff 0.60→2.00s)
- Add 5s timeout to screenshot in WebSocket push loop
- close() now wraps context.close() in asyncio.wait_for(10s)
  with browser.close() fallback on timeout
- Two-phase close logging (closing → closed / close_failed)
- Auth-capture sessions evicted after 10min TTL
- shutdown() with timeout protection and logging
- close_ok correctly tracks success through browser fallback path
2026-06-01 15:47:08 +08:00
liumangmang c8ba25f08e feat: live remote key list with auto-upsert and safe group name extraction
- list_generated_keys now fetches live keys from upstream API, merges with
  local DB: remote keys with plaintext values are auto-upserted (by
  group_id+managed_prefix), remote-only keys shown as unimportable
- Use _fetch_remote_managed_prefixes to support custom key prefixes
- Group remote keys by (group_id, prefix), pick latest by key_id
- Extract _remote_group_name helper for safe group name parsing
  (handles dict group field from Meow upstream)
- Frontend excludes orphaned keys from importable list
- Backend import endpoint reconciles upstream before importing
2026-06-01 14:53:40 +08:00
liumangmang bea4344bb3 fix: reconcile upstream keys on list/generate/import to prevent stale key imports
- Extract reconcile_upstream_keys() to website_sync.py (shared scheduler + on-demand)
- Add reconcile_upstream_keys_full() for on-demand reconciliation at three entry points:
  list_generated_keys, generate_keys_by_groups, import_upstream_keys_as_accounts
- Safe on failure: active_group_ids=None / remote_key_ids=None skip cleanup
- Support custom managed_prefix via _fetch_remote_managed_key_ids() helper
- Exclude orphaned keys from frontend importable list
- Remove hardcoded search='SmartUp' from scheduler path
2026-06-01 11:29:37 +08:00
liumangmang 3408795289 feat: default upstream balance config for new upstream form
defaultForm() now uses sub2api presets for balance fields:
- balance_endpoint: /auth/me
- balance_response_path: data.balance
- balance_divisor: 1.0

Platform presets in handlePlatformChange() remain unchanged.
2026-06-01 11:09:31 +08:00
liumangmang 92eb4888d1 test: replace TestClient with direct router calls to fix hang in binding tests
- Removed TestClient/FastAPI/app.main imports entirely
- Tests now call websites_router.create_binding() and
  websites_router.update_binding() directly with db_session
- Bypasses all ASGI transport and lifespan issues
- All 13 tests pass in 0.75s
2026-06-01 10:26:23 +08:00
liumangmang 830b6df587 test: fix hang in group binding regression tests by isolating sessions and mocking lifespan 2026-06-01 09:32:44 +08:00
liumangmang 518e3e8efc feat: add one-click sync for website group bindings 2026-06-01 09:06:01 +08:00
liumangmang 890432e1a5 test: fix RuntimeWarnings in multi-tab concurrency test 2026-05-30 10:21:30 +08:00
liumangmang 18c04c1f86 test: enhance multi-tab coverage for concurrency and events 2026-05-30 10:17:24 +08:00
liumangmang 5268f1119b fix: multi-tab concurrency and metadata sync improvements 2026-05-30 10:08:55 +08:00
liumangmang 3ab3a5e26f feat: add multi-tab support to remote browser 2026-05-30 09:51:51 +08:00
liumangmang 5c20ddc8e6 feat: sync account priorities after rate changes 2026-05-29 17:51:12 +08:00
liumangmang c5778bb3e7 feat: persist browser sessions and update admin workflows 2026-05-29 16:00:43 +08:00
SmartUp Developer e3151a7ea6 chore: untrack runtime data/ directory
data/ 包含浏览器 profile、SQLite DB、cache 等运行时生成内容,
.gitignore 已显式排除但历史上被误 track。本次 git rm --cached
解除跟踪,工作树文件保留不动。
2026-05-25 13:47:21 +08:00
SmartUp Developer 42d8731ff7 perf: website_sync 批量查询 + custom_pages origin TTL 缓存 2026-05-25 00:52:38 +08:00
SmartUp Developer 4971263a3a perf: 前端按需引入 Element Plus + Vite 拆包 + template 优化 2026-05-25 00:18:08 +08:00
SmartUp Developer 41a439d830 perf: SQLite WAL + 复合索引 + GZip + scheduler jitter + 构建缓存 2026-05-25 00:08:10 +08:00
SmartUp Developer 3a31d185a4 fix: reuse upstream keys for account import 2026-05-24 23:18:40 +08:00
liumangmang 6044b00685 feat: 上游 Key 唯一化、分组导入跳过、账号导入平台识别&远端校验&base_url 注入
- 上游 Key 命名改为 {prefix}-{upstream.id}-{safe_group_name}-{group_id}
- 唯一约束 (upstream_id, group_id, managed_prefix) 加 managed_prefix 列
- 上游检测成功时同步 Key 状态,远端已删/分组已删自动清理
- 重复分组导入跳过,目标网站已存在同名分组返回 exists
- 账号导入平台自动识别(auto/manual 模式)
- 全选可导入 Key 按钮 + 目标分组自动匹配
- 导入幂等:已导入过的 Key 校验远端账号,不存在则重建
- 新增同步接口 POST /sync-imported-upstream-keys
- account_exists() 通过拉取账号列表判断,避免 404 误判
- credentials.base_url 注入来源上游地址,避免 401
- 前端导入弹窗自动同步+刷新按钮+并发/优先级设置
- 新增 12 个测试覆盖同步、幂等、远端删除、校验失败路径
2026-05-21 01:16:39 +08:00
liumangmang 0a27bba296 fix: 修复远程浏览器登录态保留 & 剪贴板同步问题 2026-05-20 10:13:13 +08:00
liumangmang 6cc797f915 feat: remote browser login persistence + balance display + UI consistency
- Retain login state in remote browser profiles (don't delete on disconnect)
- Add GET /api/browser-sessions/{id}/clipboard for clipboard sync
- Add POST /api/browser-sessions/{id}/autofill-login for manual credential fill
- Add DELETE /api/browser-sessions/profiles/{custom_page_id} for login clear
- Add balance tracking with configurable divisor (balance_divisor)
- Health check on session reuse, idle TTL eviction, background cleanup
- Add first-frame watchdog (10s timeout) to prevent infinite loading
- Reconnect browser on active=true when session was closed
- UI: uniform text-only inline buttons (websites + upstreams pages)
- Fix page switch race with closingRemoteSessionPromise
2026-05-20 09:44:20 +08:00
SmartUp DeveloperandClaude Opus 4.7 4c71148ff9 feat: one-click upstream auth refresh from custom page viewer
- Add linked_upstream_id to CustomPage model with DB migration
- New POST /api/custom-pages/{pid}/refresh-auth endpoint extracts
  credentials from active remote browser and updates linked upstream
- PageViewer toolbar shows key icon button when page has linked upstream
- CustomPages form adds upstream dropdown for remote_browser pages
- Auth capture extracts New-Api-User from localStorage uid/user/self API
- Upstream client sends New-Api-User header in cookie auth mode
- Fix auth capture dialog: transparent background, field persistence,
  login URL defaults to base_url/login, focus on click for keyboard input
- Fix upstream test ASCII encoding with non-header characters validation

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 09:27:14 +08:00
SmartUp Developer 7cb0ff1608 fix: object URL leak, CDP before goto, limit raw secrets in extract
- AuthCaptureDialog: revokeObjectURL on each frame to prevent memory leak
- CDP Network capture starts before initial page.goto, not after
- /extract defaults to candidates-only; pass ?include_raw=true for full data
2026-05-18 14:25:56 +08:00
SmartUp Developer c7b33983d6 feat: auth-capture — WS frame stream, drag events, continuous CDP, profile cleanup
- AuthCaptureDialog: real WebSocket for binary JPEG frame stream (no polling)
- Pointer drag: mousedown/mousemove/mouseup events for slider-captcha
- CDP capture starts at session creation, caches headers in session.captured_headers
- Ephemeral profile dir deleted on session close (shutil.rmtree)
- Candidate types unified: bearer_token / cookie / api_key / credential
- Frontend handleAuthCaptureSelect maps all 4 types to correct form fields
2026-05-18 14:14:33 +08:00
SmartUp Developer 08c855677a feat: auth capture — interactive browser, CDP header capture, cookie auth
- AuthCaptureDialog: full WS screenshot stream + mouse/keyboard/scroll events
- Backend auth_capture: CDP Network.requestWillBeSent for Authorization headers
- Candidate scoring: confidence 0-95%, preview (masked), auth_headers section
- Upstream form: add 'Cookie' auth type, handle cookie selection
- UpstreamClient: support auth_type=cookie with Cookie header
- No secrets logged at DEBUG or higher
2026-05-18 11:44:10 +08:00
SmartUp Developer 4d1237c58f feat: auth capture — remote browser credential extraction
- BrowserSessionService: add create_ephemeral() for temp sessions
- New auth_capture_service.py: extract cookies, localStorage, sessionStorage from page
- New auth_capture router: POST /sessions, GET /sessions/{id}/extract, DELETE /sessions/{id}
- Frontend AuthCaptureDialog: URL input → browser view → extract → pick candidate
- Upstreams.vue: '提取' button next to Bearer Token field
- No sensitive values logged
2026-05-17 21:04:36 +08:00
SmartUp Developer c809139470 fix: authStorage key depends on pathname, F5 refresh logs out
location.pathname changes between /login and /upstreams, causing
different localStorage keys. Use static 'smartup' instead.
2026-05-17 13:01:53 +08:00
SmartUp Developer 84b714ced3 fix: sync admin password from .env on startup, add host.docker.internal for local sub2api
- _init_admin now re-hashes password if .env password differs from DB
- docker-compose: add extra_hosts host.docker.internal:host-gateway
  so containers can reach host services via host.docker.internal
2026-05-17 12:52:09 +08:00
SmartUp Developer 0ad8796285 fix: restore docker-compose env-var pattern, fix .env conflict from shell env
docker-compose.yml now reads ADMIN_PASSWORD/JWT_SECRET from env or
.env file; the shell had stale ADMIN_PASSWORD=changeme123 which
overrode .env — document this for future runs.
2026-05-17 12:40:26 +08:00
SmartUp Developer 5c60627fb6 fix: Vite type declaration, non-idempotent retry, hardcoded test IP
- Add frontend/src/vite-env.d.ts (reference vite/client) to fix vue-tsc build
- Restrict axios-retry to GET/HEAD/OPTIONS only (avoid replaying mutations)
- Convert test_upstream.py to accept URL via CLI args instead of hardcoded IP
2026-05-17 11:56:49 +08:00
SmartUp Developer 2934473770 fix: remove stale _decimal_str ref, add context manager to HTTP clients
- UpstreamClient & Sub2ApiWebsiteClient: add __enter__/__exit__
- Convert all call sites to `with Client(...) as c:` pattern
- Remove unused `upstream_name`/`upstream_base_url` locals in scheduler
- Fix stale _decimal_str→decimal_string in _rate_from_group
2026-05-17 11:29:51 +08:00
SmartUp Developer 8a6ed249be fix: complete remaining 8 optimization items
- HTTP connection pooling: UpstreamClient & WebsiteClient reuse httpx.Client
- Deduplicate decimal_string into shared app/utils/number.py
- Split scheduler transaction: snapshot write → webhook/website sync in separate sessions
- Remove hardcoded 170.106.100.210 migration from database.py
- Reset consecutive_failures on upstream update
- Healthcheck: install curl, replace python -c with curl -f
- Add .dockerignore to reduce build context
- Frontend: add axios-retry with exponential backoff (5xx/network errors only)
2026-05-17 11:09:35 +08:00
SmartUp Developer ad16618406 fix: address multiple code audit findings
- CORS: replace wildcard with explicit origin list from CORS_ORIGINS env
- Auth: enforce strong defaults, JWT blacklist (RevokedToken model), login rate limiting
- Auth: validate password length before bcrypt (72-byte limit)
- Scheduler: single-threaded worker to mitigate SQLite write contention
- Scheduler: graceful shutdown (wait=True)
- Snapshots: add prune_snapshots() with configurable retention count
- Storage: isolate localStorage keys via VITE_APP_KEY prefix
- Config: add cors_origins, login_rate_limit, snapshot_retention_count settings
2026-05-17 10:52:18 +08:00