mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-11 18:44:45 +08:00
feat(antigravity): add 403 forbidden status detection, classification and display
Backend: - Detect and classify 403 responses into three types: validation (account needs Google verification), violation (terms of service / banned), forbidden (generic 403) - Extract verification/appeal URLs from 403 response body (structured JSON parsing with regex fallback) - Add needs_verify, is_banned, needs_reauth, error_code fields to UsageInfo (omitempty for zero impact on other platforms) - Handle 403 in request path: classify and permanently set account error - Save validation_url in error_message for degraded path recovery - Enrich usage with account error on both success and degraded paths - Add singleflight dedup for usage requests with independent context - Differentiate cache TTL: success/403 → 3min, errors → 1min - Return degraded UsageInfo instead of HTTP 500 on quota fetch errors Frontend: - Display forbidden status badges with color coding (red for banned, amber for needs verification, gray for generic) - Show clickable verification/appeal URL links - Display needs_reauth and degraded error states in usage cell - Add Antigravity tier label badge next to platform type Tests: - Comprehensive unit tests for classifyForbiddenType (7 cases) - Unit tests for extractValidationURL (8 cases including unicode escapes) - Integration test for FetchQuota forbidden path
This commit is contained in:
@@ -171,7 +171,15 @@
|
||||
<span v-else class="text-sm text-gray-400 dark:text-dark-500">-</span>
|
||||
</template>
|
||||
<template #cell-platform_type="{ row }">
|
||||
<PlatformTypeBadge :platform="row.platform" :type="row.type" :plan-type="row.credentials?.plan_type" :privacy-mode="row.extra?.privacy_mode" />
|
||||
<div class="flex flex-wrap items-center gap-1">
|
||||
<PlatformTypeBadge :platform="row.platform" :type="row.type" :plan-type="row.credentials?.plan_type" :privacy-mode="row.extra?.privacy_mode" />
|
||||
<span
|
||||
v-if="getAntigravityTierLabel(row)"
|
||||
:class="['inline-block rounded px-1.5 py-0.5 text-[10px] font-medium', getAntigravityTierClass(row)]"
|
||||
>
|
||||
{{ getAntigravityTierLabel(row) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell-capacity="{ row }">
|
||||
<AccountCapacityCell :account="row" />
|
||||
@@ -794,6 +802,40 @@ const { pause: pauseAutoRefresh, resume: resumeAutoRefresh } = useIntervalFn(
|
||||
{ immediate: false }
|
||||
)
|
||||
|
||||
// Antigravity 订阅等级辅助函数
|
||||
function getAntigravityTierFromRow(row: any): string | null {
|
||||
if (row.platform !== 'antigravity') return null
|
||||
const extra = row.extra as Record<string, unknown> | undefined
|
||||
if (!extra) return null
|
||||
const lca = extra.load_code_assist as Record<string, unknown> | undefined
|
||||
if (!lca) return null
|
||||
const paid = lca.paidTier as Record<string, unknown> | undefined
|
||||
if (paid && typeof paid.id === 'string') return paid.id
|
||||
const current = lca.currentTier as Record<string, unknown> | undefined
|
||||
if (current && typeof current.id === 'string') return current.id
|
||||
return null
|
||||
}
|
||||
|
||||
function getAntigravityTierLabel(row: any): string | null {
|
||||
const tier = getAntigravityTierFromRow(row)
|
||||
switch (tier) {
|
||||
case 'free-tier': return t('admin.accounts.tier.free')
|
||||
case 'g1-pro-tier': return t('admin.accounts.tier.pro')
|
||||
case 'g1-ultra-tier': return t('admin.accounts.tier.ultra')
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
|
||||
function getAntigravityTierClass(row: any): string {
|
||||
const tier = getAntigravityTierFromRow(row)
|
||||
switch (tier) {
|
||||
case 'free-tier': return 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
|
||||
case 'g1-pro-tier': return 'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
||||
case 'g1-ultra-tier': return 'bg-purple-100 text-purple-600 dark:bg-purple-900/40 dark:text-purple-300'
|
||||
default: return ''
|
||||
}
|
||||
}
|
||||
|
||||
// All available columns
|
||||
const allColumns = computed(() => {
|
||||
const c = [
|
||||
|
||||
Reference in New Issue
Block a user