diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go
index 41804775..3c1e8ce4 100644
--- a/backend/cmd/server/wire_gen.go
+++ b/backend/cmd/server/wire_gen.go
@@ -84,7 +84,9 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher)
httpUpstream := repository.NewHTTPUpstream(configConfig)
accountTestService := service.NewAccountTestService(accountRepository, oAuthService, openAIOAuthService, httpUpstream)
- accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, rateLimitService, accountUsageService, accountTestService)
+ concurrencyCache := repository.NewConcurrencyCache(client)
+ concurrencyService := service.NewConcurrencyService(concurrencyCache)
+ accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService)
oAuthHandler := admin.NewOAuthHandler(oAuthService)
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService)
proxyHandler := admin.NewProxyHandler(adminService)
@@ -108,8 +110,6 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
identityCache := repository.NewIdentityCache(client)
identityService := service.NewIdentityService(identityCache)
gatewayService := service.NewGatewayService(accountRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, billingService, rateLimitService, billingCacheService, identityService, httpUpstream)
- concurrencyCache := repository.NewConcurrencyCache(client)
- concurrencyService := service.NewConcurrencyService(concurrencyCache)
gatewayHandler := handler.NewGatewayHandler(gatewayService, userService, concurrencyService, billingCacheService)
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, billingService, rateLimitService, billingCacheService, httpUpstream)
openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService)
diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go
index 74b26187..13ce453b 100644
--- a/backend/internal/handler/admin/account_handler.go
+++ b/backend/internal/handler/admin/account_handler.go
@@ -3,6 +3,7 @@ package admin
import (
"strconv"
+ "sub2api/internal/model"
"sub2api/internal/pkg/claude"
"sub2api/internal/pkg/openai"
"sub2api/internal/pkg/response"
@@ -32,10 +33,11 @@ type AccountHandler struct {
rateLimitService *service.RateLimitService
accountUsageService *service.AccountUsageService
accountTestService *service.AccountTestService
+ concurrencyService *service.ConcurrencyService
}
// NewAccountHandler creates a new admin account handler
-func NewAccountHandler(adminService service.AdminService, oauthService *service.OAuthService, openaiOAuthService *service.OpenAIOAuthService, rateLimitService *service.RateLimitService, accountUsageService *service.AccountUsageService, accountTestService *service.AccountTestService) *AccountHandler {
+func NewAccountHandler(adminService service.AdminService, oauthService *service.OAuthService, openaiOAuthService *service.OpenAIOAuthService, rateLimitService *service.RateLimitService, accountUsageService *service.AccountUsageService, accountTestService *service.AccountTestService, concurrencyService *service.ConcurrencyService) *AccountHandler {
return &AccountHandler{
adminService: adminService,
oauthService: oauthService,
@@ -43,6 +45,7 @@ func NewAccountHandler(adminService service.AdminService, oauthService *service.
rateLimitService: rateLimitService,
accountUsageService: accountUsageService,
accountTestService: accountTestService,
+ concurrencyService: concurrencyService,
}
}
@@ -73,6 +76,12 @@ type UpdateAccountRequest struct {
GroupIDs *[]int64 `json:"group_ids"`
}
+// AccountWithConcurrency extends Account with real-time concurrency info
+type AccountWithConcurrency struct {
+ *model.Account
+ CurrentConcurrency int `json:"current_concurrency"`
+}
+
// List handles listing all accounts with pagination
// GET /api/v1/admin/accounts
func (h *AccountHandler) List(c *gin.Context) {
@@ -88,7 +97,28 @@ func (h *AccountHandler) List(c *gin.Context) {
return
}
- response.Paginated(c, accounts, total, page, pageSize)
+ // Get current concurrency counts for all accounts
+ accountIDs := make([]int64, len(accounts))
+ for i, acc := range accounts {
+ accountIDs[i] = acc.ID
+ }
+
+ concurrencyCounts, err := h.concurrencyService.GetAccountConcurrencyBatch(c.Request.Context(), accountIDs)
+ if err != nil {
+ // Log error but don't fail the request, just use 0 for all
+ concurrencyCounts = make(map[int64]int)
+ }
+
+ // Build response with concurrency info
+ result := make([]AccountWithConcurrency, len(accounts))
+ for i := range accounts {
+ result[i] = AccountWithConcurrency{
+ Account: &accounts[i],
+ CurrentConcurrency: concurrencyCounts[accounts[i].ID],
+ }
+ }
+
+ response.Paginated(c, result, total, page, pageSize)
}
// GetByID handles getting an account by ID
diff --git a/backend/internal/service/concurrency_service.go b/backend/internal/service/concurrency_service.go
index c54167da..c554624c 100644
--- a/backend/internal/service/concurrency_service.go
+++ b/backend/internal/service/concurrency_service.go
@@ -147,3 +147,20 @@ func CalculateMaxWait(userConcurrency int) int {
}
return userConcurrency + defaultExtraWaitSlots
}
+
+// GetAccountConcurrencyBatch gets current concurrency counts for multiple accounts
+// Returns a map of accountID -> current concurrency count
+func (s *ConcurrencyService) GetAccountConcurrencyBatch(ctx context.Context, accountIDs []int64) (map[int64]int, error) {
+ result := make(map[int64]int)
+
+ for _, accountID := range accountIDs {
+ count, err := s.cache.GetAccountConcurrency(ctx, accountID)
+ if err != nil {
+ // If key doesn't exist in Redis, count is 0
+ count = 0
+ }
+ result[accountID] = count
+ }
+
+ return result, nil
+}
diff --git a/frontend/src/components/common/PlatformTypeBadge.vue b/frontend/src/components/common/PlatformTypeBadge.vue
new file mode 100644
index 00000000..d7ed6714
--- /dev/null
+++ b/frontend/src/components/common/PlatformTypeBadge.vue
@@ -0,0 +1,75 @@
+
+
+
+
+
+ {{ platformLabel }}
+
+
+
+
+
+
+
+
+
+ {{ typeLabel }}
+
+
+
+
+
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts
index a81f6706..1d063f57 100644
--- a/frontend/src/i18n/locales/en.ts
+++ b/frontend/src/i18n/locales/en.ts
@@ -702,8 +702,10 @@ export default {
},
columns: {
name: 'Name',
+ platformType: 'Platform/Type',
platform: 'Platform',
type: 'Type',
+ concurrencyStatus: 'Concurrency',
status: 'Status',
schedulable: 'Schedule',
todayStats: "Today's Stats",
diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts
index 4cd4214b..d71148ae 100644
--- a/frontend/src/i18n/locales/zh.ts
+++ b/frontend/src/i18n/locales/zh.ts
@@ -796,8 +796,10 @@ export default {
failedToToggleSchedulable: '切换调度状态失败',
columns: {
name: '名称',
+ platformType: '平台/类型',
platform: '平台',
type: '类型',
+ concurrencyStatus: '并发',
priority: '优先级',
weight: '权重',
status: '状态',
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts
index 7fea7ad9..17b3b9db 100644
--- a/frontend/src/types/index.ts
+++ b/frontend/src/types/index.ts
@@ -319,6 +319,7 @@ export interface Account {
extra?: CodexUsageSnapshot & Record; // Extra fields including Codex usage
proxy_id: number | null;
concurrency: number;
+ current_concurrency?: number; // Real-time concurrency count from Redis
priority: number;
status: 'active' | 'inactive' | 'error';
error_message: string | null;
diff --git a/frontend/src/views/admin/AccountsView.vue b/frontend/src/views/admin/AccountsView.vue
index fca29988..b9903a38 100644
--- a/frontend/src/views/admin/AccountsView.vue
+++ b/frontend/src/views/admin/AccountsView.vue
@@ -73,29 +73,30 @@
{{ value }}
-
-
-
- {{ value === 'anthropic' ? 'Anthropic' : 'OpenAI' }}
-
+
+
-
-
- {{ value === 'oauth' ? 'Oauth' : value === 'setup-token' ? t('admin.accounts.setupToken') : t('admin.accounts.apiKey') }}
-
+
+
+
+
+ {{ row.current_concurrency || 0 }}
+ /
+ {{ row.concurrency }}
+
+
@@ -336,7 +337,7 @@ import AccountUsageCell from '@/components/account/AccountUsageCell.vue'
import AccountTodayStatsCell from '@/components/account/AccountTodayStatsCell.vue'
import AccountTestModal from '@/components/account/AccountTestModal.vue'
import GroupBadge from '@/components/common/GroupBadge.vue'
-import PlatformIcon from '@/components/common/PlatformIcon.vue'
+import PlatformTypeBadge from '@/components/common/PlatformTypeBadge.vue'
import { formatRelativeTime } from '@/utils/format'
const { t } = useI18n()
@@ -345,8 +346,8 @@ const appStore = useAppStore()
// Table columns
const columns = computed(() => [
{ key: 'name', label: t('admin.accounts.columns.name'), sortable: true },
- { key: 'platform', label: t('admin.accounts.columns.platform'), sortable: true },
- { key: 'type', label: t('admin.accounts.columns.type'), sortable: true },
+ { key: 'platform_type', label: t('admin.accounts.columns.platformType'), sortable: false },
+ { key: 'concurrency', label: t('admin.accounts.columns.concurrencyStatus'), sortable: false },
{ key: 'status', label: t('admin.accounts.columns.status'), sortable: true },
{ key: 'schedulable', label: t('admin.accounts.columns.schedulable'), sortable: true },
{ key: 'today_stats', label: t('admin.accounts.columns.todayStats'), sortable: false },