pq driver treats []byte as bytea, not jsonb. Convert charJSON and
reqBody to *string before passing to ExecContext for character_info
and request_body JSONB columns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New API: GET /accounts/:id/affinity-clients returns client list with
last_active timestamps from Redis ZSET scores
- New Lua script: get_affinity_clients_with_scores.lua uses ZREVRANGEBYSCORE
WITHSCORES to return both client IDs and their timestamps
- New frontend component: AffinityBadge.vue replaces inline badge with
hover popover that lazy-loads client details on first hover
- Shows client ID (monospace) + relative time (e.g. "3h ago")
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pipeline mode only sends EVALSHA (no automatic fallback to EVAL).
If Redis restarts and script cache is lost, all pipeline Lua calls
silently fail with NOSCRIPT. Add ensureScriptLoaded() to pre-load
scripts before pipeline execution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously the count was only returned when Redis had data, causing
the badge to not display for accounts with zero affinity clients.
Now returns count=0 for all affinity-enabled accounts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix struct field alignment in dto/types.go, settings_view.go,
sora_generation_service.go for gofmt compliance
- Remove embedded field "Account" from selector in account_handler.go (QF1008)
- Remove unused settingService field from SoraGDriveOAuthService
- Replace deprecated google.CredentialsFromJSON with CredentialsFromJSONWithParams (SA1019)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add colored circle badge showing affinity client count before the account
status indicator. Badge color reflects count severity (gray=0, green=1-5,
yellow=6-15, red=16+). Tooltip shows full client ID list on hover.
Backend: AccountHandler batch queries reverse affinity index via
GetAccountAffinityClientsBatch, returns affinity_client_count and
affinity_clients in DTO. GatewayCache interface extended with the new
batch method backed by a Lua script (get_affinity_clients.lua).
All test mocks synchronized with updated GatewayCache interface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Split compound "Profile" column into separate Profile ID and Name columns
- Replace "Endpoint" column with "Storage Path" showing bucket/prefix or folder info
- Add GDrive quota display (capacity/used) via new backend API endpoint
- Add video count statistics per storage type via new backend API endpoint
- Add inline test button per profile with 15s timeout and per-profile loading state
- Backend: GetQuotaInfo, CountByStorageType, GetGDriveQuota, GetStorageVideoStats
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Define SoraObjectStorage interface abstracting S3 and GDrive backends
- Implement SoraGDriveStorage with OAuth2 and Service Account auth
- Add SoraStorageRouter to route requests based on active profile provider
- Add GDrive OAuth handler for authorization flow (start + callback)
- Extend profile model with provider, auth_type, and GDrive-specific fields
- Update frontend UI with provider selection, dynamic form fields, and i18n
- Migrate API paths from /sora-s3 to /sora-storage (old paths preserved)
- All existing S3 functionality remains backward compatible
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove frontend badge/tooltip and backend counting/query infrastructure
for client affinity. Core client affinity scheduling logic is preserved.
Removed:
- GatewayCache injection in AccountHandler
- GetAccountAffinityCount/Batch/Clients methods and interface
- AffinityClientInfo struct and ClientAffinityTTL function
- /affinity-clients API endpoint and route
- Reverse index (account_affinity:*) Lua scripts
- Frontend blue badge, tooltip, and lazy-load logic
- affinity_client_count field from types and response
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add reverse Redis index (account_affinity:{accountID}) to track which
clients are bound to each account via affinity scheduling. Display a
blue badge with client count in the admin account list, with a hover
tooltip showing client details (groupID:clientID + TTL).
Backend: reverse index in Lua scripts, batch query methods, new
GET /accounts/:id/affinity-clients endpoint.
Frontend: blue badge + lazy-loaded tooltip in AccountStatusIndicator.
- Separate load factor from concurrency: concurrency controls actual
slot acquisition, load_factor controls load rate calculation
- Add EffectiveLoadFactor() method: LoadFactor > Concurrency > 1
- Add load_factor field to Create/Edit/BulkEdit account forms
- Fix RPM default value: auto-fill 15 when RPM enabled but not set
- Fix stale test compilation errors in server and handler packages
1. (Critical) Filter admin-only menu items from public API responses -
both GetPublicSettings handler and GetPublicSettingsForInjection now
exclude visibility=admin items, preventing unauthorized access to
admin menu URLs.
2. (Medium) Validate JSON array structure in sanitizeCustomMenuItemsJSON -
use json.Unmarshal into []json.RawMessage instead of json.Valid to
reject non-array JSON values that would cause frontend runtime errors.
3. (Medium) Decouple router from business JSON parsing - move origin
extraction logic from router.go to SettingService.GetFrameSrcOrigins,
eliminating direct JSON parsing of custom_menu_items in the routing
layer.
4. (Low) Restrict custom menu item ID charset to [a-zA-Z0-9_-] via
regex validation, preventing route-breaking characters like / ? # or
spaces.
5. (Low) Handle crypto/rand error in generateMenuItemID - return error
instead of silently ignoring, preventing potential duplicate IDs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add admin menu permission check in CustomPageView (visibility + role)
- Sanitize SVG content with DOMPurify before v-html rendering (XSS prevention)
- Decouple router.go from dto package using anonymous struct
- Consolidate duplicate parseCustomMenuItems into dto.ParseCustomMenuItems
- Enhance menu item validation (count, length, ID uniqueness limits)
- Add audit logging for purchase_subscription and custom_menu_items changes
- Update API contract test to include custom_menu_items field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add configurable custom menu items that appear in sidebar, each rendering
an iframe-embedded external page. Includes shared URL builder with
src_host/src_url tracking, CSP frame-src multi-origin deduplication,
admin settings UI, and i18n support.
chore: bump version to 0.1.87.19
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- add default subscriptions to admin settings
- auto-assign subscriptions on register and admin user creation
- add validation/tests and align settings UI with subscription selector patterns
Add a doughnut chart showing usage statistics broken down by group on
the admin usage records page. The chart appears alongside the existing
model distribution chart (2-column grid), with the token usage trend
chart moved to a separate full-width row below.
Changes:
- backend/pkg/usagestats: add GroupStat type
- backend/service: add GetGroupStatsWithFilters interface method and implementation
- backend/repository: implement GetGroupStatsWithFilters with LEFT JOIN groups
- backend/handler: add GetGroupStats handler with full filter support
- backend/routes: register GET /admin/dashboard/groups route
- backend/tests: add GetGroupStatsWithFilters stubs to contract/sora tests
- frontend/types: add GroupStat interface
- frontend/api: add getGroupStats API function and types
- frontend/components: add GroupDistributionChart.vue doughnut chart
- frontend/views: update UsageView layout and load group stats in parallel
- frontend/i18n: add groupDistribution, group, noGroup keys (zh + en)
- Add GroupStat type to usagestats package
- Add GetGroupStatsWithFilters to UsageLogRepository interface and implement with LEFT JOIN groups
- Add GetGroupStats dashboard API endpoint (GET /admin/dashboard/groups)
- Add GroupDistributionChart.vue component mirroring ModelDistributionChart
- Rearrange UsageView layout: model + group in one row, token trend full-width below
- All filters (user, api_key, account, group, model, date range) apply to group stats