feat(ops): 运维界面展示 Antigravity 账号 scope 级别限流统计
在运维监控的并发/排队卡片中,为 Antigravity 平台账号显示各 scope
(claude/gemini_text/gemini_image) 的限流数量统计,便于管理员了解
哪些 scope 正在被限流。
Cherry-picked from slovx2/sub2api: 08d6dc52
This commit is contained in:
@@ -89,3 +89,30 @@ func (a *Account) antigravityQuotaScopeResetAt(scope AntigravityQuotaScope) *tim
|
|||||||
}
|
}
|
||||||
return &resetAt
|
return &resetAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var antigravityAllScopes = []AntigravityQuotaScope{
|
||||||
|
AntigravityQuotaScopeClaude,
|
||||||
|
AntigravityQuotaScopeGeminiText,
|
||||||
|
AntigravityQuotaScopeGeminiImage,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Account) GetAntigravityScopeRateLimits() map[string]int64 {
|
||||||
|
if a == nil || a.Platform != PlatformAntigravity {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
result := make(map[string]int64)
|
||||||
|
for _, scope := range antigravityAllScopes {
|
||||||
|
resetAt := a.antigravityQuotaScopeResetAt(scope)
|
||||||
|
if resetAt != nil && now.Before(*resetAt) {
|
||||||
|
remainingSec := int64(time.Until(*resetAt).Seconds())
|
||||||
|
if remainingSec > 0 {
|
||||||
|
result[string(scope)] = remainingSec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(result) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ func (s *OpsService) GetAccountAvailabilityStats(ctx context.Context, platformFi
|
|||||||
|
|
||||||
isAvailable := acc.Status == StatusActive && acc.Schedulable && !isRateLimited && !isOverloaded && !isTempUnsched
|
isAvailable := acc.Status == StatusActive && acc.Schedulable && !isRateLimited && !isOverloaded && !isTempUnsched
|
||||||
|
|
||||||
|
scopeRateLimits := acc.GetAntigravityScopeRateLimits()
|
||||||
|
|
||||||
if acc.Platform != "" {
|
if acc.Platform != "" {
|
||||||
if _, ok := platform[acc.Platform]; !ok {
|
if _, ok := platform[acc.Platform]; !ok {
|
||||||
platform[acc.Platform] = &PlatformAvailability{
|
platform[acc.Platform] = &PlatformAvailability{
|
||||||
@@ -84,6 +86,14 @@ func (s *OpsService) GetAccountAvailabilityStats(ctx context.Context, platformFi
|
|||||||
if hasError {
|
if hasError {
|
||||||
p.ErrorCount++
|
p.ErrorCount++
|
||||||
}
|
}
|
||||||
|
if len(scopeRateLimits) > 0 {
|
||||||
|
if p.ScopeRateLimitCount == nil {
|
||||||
|
p.ScopeRateLimitCount = make(map[string]int64)
|
||||||
|
}
|
||||||
|
for scope := range scopeRateLimits {
|
||||||
|
p.ScopeRateLimitCount[scope]++
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, grp := range acc.Groups {
|
for _, grp := range acc.Groups {
|
||||||
@@ -108,6 +118,14 @@ func (s *OpsService) GetAccountAvailabilityStats(ctx context.Context, platformFi
|
|||||||
if hasError {
|
if hasError {
|
||||||
g.ErrorCount++
|
g.ErrorCount++
|
||||||
}
|
}
|
||||||
|
if len(scopeRateLimits) > 0 {
|
||||||
|
if g.ScopeRateLimitCount == nil {
|
||||||
|
g.ScopeRateLimitCount = make(map[string]int64)
|
||||||
|
}
|
||||||
|
for scope := range scopeRateLimits {
|
||||||
|
g.ScopeRateLimitCount[scope]++
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
displayGroupID := int64(0)
|
displayGroupID := int64(0)
|
||||||
@@ -140,6 +158,9 @@ func (s *OpsService) GetAccountAvailabilityStats(ctx context.Context, platformFi
|
|||||||
item.RateLimitRemainingSec = &remainingSec
|
item.RateLimitRemainingSec = &remainingSec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(scopeRateLimits) > 0 {
|
||||||
|
item.ScopeRateLimits = scopeRateLimits
|
||||||
|
}
|
||||||
if isOverloaded && acc.OverloadUntil != nil {
|
if isOverloaded && acc.OverloadUntil != nil {
|
||||||
item.OverloadUntil = acc.OverloadUntil
|
item.OverloadUntil = acc.OverloadUntil
|
||||||
remainingSec := int64(time.Until(*acc.OverloadUntil).Seconds())
|
remainingSec := int64(time.Until(*acc.OverloadUntil).Seconds())
|
||||||
|
|||||||
@@ -39,22 +39,24 @@ type AccountConcurrencyInfo struct {
|
|||||||
|
|
||||||
// PlatformAvailability aggregates account availability by platform.
|
// PlatformAvailability aggregates account availability by platform.
|
||||||
type PlatformAvailability struct {
|
type PlatformAvailability struct {
|
||||||
Platform string `json:"platform"`
|
Platform string `json:"platform"`
|
||||||
TotalAccounts int64 `json:"total_accounts"`
|
TotalAccounts int64 `json:"total_accounts"`
|
||||||
AvailableCount int64 `json:"available_count"`
|
AvailableCount int64 `json:"available_count"`
|
||||||
RateLimitCount int64 `json:"rate_limit_count"`
|
RateLimitCount int64 `json:"rate_limit_count"`
|
||||||
ErrorCount int64 `json:"error_count"`
|
ScopeRateLimitCount map[string]int64 `json:"scope_rate_limit_count,omitempty"`
|
||||||
|
ErrorCount int64 `json:"error_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupAvailability aggregates account availability by group.
|
// GroupAvailability aggregates account availability by group.
|
||||||
type GroupAvailability struct {
|
type GroupAvailability struct {
|
||||||
GroupID int64 `json:"group_id"`
|
GroupID int64 `json:"group_id"`
|
||||||
GroupName string `json:"group_name"`
|
GroupName string `json:"group_name"`
|
||||||
Platform string `json:"platform"`
|
Platform string `json:"platform"`
|
||||||
TotalAccounts int64 `json:"total_accounts"`
|
TotalAccounts int64 `json:"total_accounts"`
|
||||||
AvailableCount int64 `json:"available_count"`
|
AvailableCount int64 `json:"available_count"`
|
||||||
RateLimitCount int64 `json:"rate_limit_count"`
|
RateLimitCount int64 `json:"rate_limit_count"`
|
||||||
ErrorCount int64 `json:"error_count"`
|
ScopeRateLimitCount map[string]int64 `json:"scope_rate_limit_count,omitempty"`
|
||||||
|
ErrorCount int64 `json:"error_count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountAvailability represents current availability for a single account.
|
// AccountAvailability represents current availability for a single account.
|
||||||
@@ -72,10 +74,11 @@ type AccountAvailability struct {
|
|||||||
IsOverloaded bool `json:"is_overloaded"`
|
IsOverloaded bool `json:"is_overloaded"`
|
||||||
HasError bool `json:"has_error"`
|
HasError bool `json:"has_error"`
|
||||||
|
|
||||||
RateLimitResetAt *time.Time `json:"rate_limit_reset_at"`
|
RateLimitResetAt *time.Time `json:"rate_limit_reset_at"`
|
||||||
RateLimitRemainingSec *int64 `json:"rate_limit_remaining_sec"`
|
RateLimitRemainingSec *int64 `json:"rate_limit_remaining_sec"`
|
||||||
OverloadUntil *time.Time `json:"overload_until"`
|
ScopeRateLimits map[string]int64 `json:"scope_rate_limits,omitempty"`
|
||||||
OverloadRemainingSec *int64 `json:"overload_remaining_sec"`
|
OverloadUntil *time.Time `json:"overload_until"`
|
||||||
ErrorMessage string `json:"error_message"`
|
OverloadRemainingSec *int64 `json:"overload_remaining_sec"`
|
||||||
TempUnschedulableUntil *time.Time `json:"temp_unschedulable_until,omitempty"`
|
ErrorMessage string `json:"error_message"`
|
||||||
|
TempUnschedulableUntil *time.Time `json:"temp_unschedulable_until,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,6 +353,7 @@ export interface PlatformAvailability {
|
|||||||
total_accounts: number
|
total_accounts: number
|
||||||
available_count: number
|
available_count: number
|
||||||
rate_limit_count: number
|
rate_limit_count: number
|
||||||
|
scope_rate_limit_count?: Record<string, number>
|
||||||
error_count: number
|
error_count: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,6 +364,7 @@ export interface GroupAvailability {
|
|||||||
total_accounts: number
|
total_accounts: number
|
||||||
available_count: number
|
available_count: number
|
||||||
rate_limit_count: number
|
rate_limit_count: number
|
||||||
|
scope_rate_limit_count?: Record<string, number>
|
||||||
error_count: number
|
error_count: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,6 +379,7 @@ export interface AccountAvailability {
|
|||||||
is_rate_limited: boolean
|
is_rate_limited: boolean
|
||||||
rate_limit_reset_at?: string
|
rate_limit_reset_at?: string
|
||||||
rate_limit_remaining_sec?: number
|
rate_limit_remaining_sec?: number
|
||||||
|
scope_rate_limits?: Record<string, number>
|
||||||
is_overloaded: boolean
|
is_overloaded: boolean
|
||||||
overload_until?: string
|
overload_until?: string
|
||||||
overload_remaining_sec?: number
|
overload_remaining_sec?: number
|
||||||
|
|||||||
@@ -2829,6 +2829,7 @@ export default {
|
|||||||
empty: 'No data',
|
empty: 'No data',
|
||||||
queued: 'Queue {count}',
|
queued: 'Queue {count}',
|
||||||
rateLimited: 'Rate-limited {count}',
|
rateLimited: 'Rate-limited {count}',
|
||||||
|
scopeRateLimitedTooltip: '{scope} rate-limited ({count} accounts)',
|
||||||
errorAccounts: 'Errors {count}',
|
errorAccounts: 'Errors {count}',
|
||||||
loadFailed: 'Failed to load concurrency data'
|
loadFailed: 'Failed to load concurrency data'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2982,6 +2982,7 @@ export default {
|
|||||||
empty: '暂无数据',
|
empty: '暂无数据',
|
||||||
queued: '队列 {count}',
|
queued: '队列 {count}',
|
||||||
rateLimited: '限流 {count}',
|
rateLimited: '限流 {count}',
|
||||||
|
scopeRateLimitedTooltip: '{scope} 限流中 ({count} 个账号)',
|
||||||
errorAccounts: '异常 {count}',
|
errorAccounts: '异常 {count}',
|
||||||
loadFailed: '加载并发数据失败'
|
loadFailed: '加载并发数据失败'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ interface SummaryRow {
|
|||||||
total_accounts: number
|
total_accounts: number
|
||||||
available_accounts: number
|
available_accounts: number
|
||||||
rate_limited_accounts: number
|
rate_limited_accounts: number
|
||||||
|
scope_rate_limit_count?: Record<string, number>
|
||||||
error_accounts: number
|
error_accounts: number
|
||||||
// 并发统计
|
// 并发统计
|
||||||
total_concurrency: number
|
total_concurrency: number
|
||||||
@@ -102,6 +103,7 @@ const platformRows = computed((): SummaryRow[] => {
|
|||||||
total_accounts: totalAccounts,
|
total_accounts: totalAccounts,
|
||||||
available_accounts: availableAccounts,
|
available_accounts: availableAccounts,
|
||||||
rate_limited_accounts: safeNumber(avail.rate_limit_count),
|
rate_limited_accounts: safeNumber(avail.rate_limit_count),
|
||||||
|
scope_rate_limit_count: avail.scope_rate_limit_count,
|
||||||
error_accounts: safeNumber(avail.error_count),
|
error_accounts: safeNumber(avail.error_count),
|
||||||
total_concurrency: totalConcurrency,
|
total_concurrency: totalConcurrency,
|
||||||
used_concurrency: usedConcurrency,
|
used_concurrency: usedConcurrency,
|
||||||
@@ -141,6 +143,7 @@ const groupRows = computed((): SummaryRow[] => {
|
|||||||
total_accounts: totalAccounts,
|
total_accounts: totalAccounts,
|
||||||
available_accounts: availableAccounts,
|
available_accounts: availableAccounts,
|
||||||
rate_limited_accounts: safeNumber(avail.rate_limit_count),
|
rate_limited_accounts: safeNumber(avail.rate_limit_count),
|
||||||
|
scope_rate_limit_count: avail.scope_rate_limit_count,
|
||||||
error_accounts: safeNumber(avail.error_count),
|
error_accounts: safeNumber(avail.error_count),
|
||||||
total_concurrency: totalConcurrency,
|
total_concurrency: totalConcurrency,
|
||||||
used_concurrency: usedConcurrency,
|
used_concurrency: usedConcurrency,
|
||||||
@@ -269,6 +272,15 @@ function formatDuration(seconds: number): string {
|
|||||||
return `${hours}h`
|
return `${hours}h`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatScopeName(scope: string): string {
|
||||||
|
const names: Record<string, string> = {
|
||||||
|
claude: 'Claude',
|
||||||
|
gemini_text: 'Gemini',
|
||||||
|
gemini_image: 'Image'
|
||||||
|
}
|
||||||
|
return names[scope] || scope
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => realtimeEnabled.value,
|
() => realtimeEnabled.value,
|
||||||
async (enabled) => {
|
async (enabled) => {
|
||||||
@@ -387,6 +399,18 @@ watch(
|
|||||||
{{ t('admin.ops.concurrency.rateLimited', { count: row.rate_limited_accounts }) }}
|
{{ t('admin.ops.concurrency.rateLimited', { count: row.rate_limited_accounts }) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<!-- Scope 限流 (仅 Antigravity) -->
|
||||||
|
<template v-if="row.scope_rate_limit_count && Object.keys(row.scope_rate_limit_count).length > 0">
|
||||||
|
<span
|
||||||
|
v-for="(count, scope) in row.scope_rate_limit_count"
|
||||||
|
:key="scope"
|
||||||
|
class="rounded-full bg-orange-100 px-1.5 py-0.5 font-semibold text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
|
||||||
|
:title="t('admin.ops.concurrency.scopeRateLimitedTooltip', { scope, count })"
|
||||||
|
>
|
||||||
|
{{ formatScopeName(scope as string) }} {{ count }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 异常账号 -->
|
<!-- 异常账号 -->
|
||||||
<span
|
<span
|
||||||
v-if="row.error_accounts > 0"
|
v-if="row.error_accounts > 0"
|
||||||
|
|||||||
Reference in New Issue
Block a user