feat: OAuth 密钥编辑时显示到期时间和重新授权按钮

- 后端 EndpointAPIKeyResponse 新增 oauth_expires_at 字段
- 从加密的 auth_config 中解密并提取 OAuth Token 到期时间
- 前端编辑已授权的 OAuth 密钥时显示"重新授权"按钮
- 在按钮旁显示 Token 到期时间
This commit is contained in:
AAEE86
2026-02-04 16:05:25 +08:00
parent cb4407ba49
commit 4c1ec66365
4 changed files with 35 additions and 17 deletions
+2
View File
@@ -247,6 +247,8 @@ export interface EndpointAPIKey {
// 模型过滤规则(仅当 auto_fetch_models=true 时生效)
model_include_patterns?: string[] // 模型包含规则(支持 * 和 ? 通配符)
model_exclude_patterns?: string[] // 模型排除规则(支持 * 和 ? 通配符)
// OAuth 相关
oauth_expires_at?: number | null // OAuth Token 过期时间(Unix 时间戳)
}
// 按格式的健康度数据
@@ -144,14 +144,14 @@
{{ oauthHelpText }}
</p>
<div class="flex flex-wrap gap-2">
<div class="flex flex-wrap gap-2 items-center">
<Button
size="sm"
type="button"
:disabled="!canStartOAuth"
@click="handleStartOAuth"
>
{{ oauth.starting ? '开始中...' : '开始授权' }}
{{ oauth.starting ? '开始中...' : (oauth.step === 'completed' ? '重新授权' : '开始授权') }}
</Button>
<Button
@@ -164,6 +164,14 @@
<RefreshCw class="w-3.5 h-3.5 mr-1.5" />
{{ oauth.refreshing ? '刷新中...' : '强制刷新' }}
</Button>
<span
v-if="oauth.step === 'completed' && oauth.expires_at"
class="text-xs text-muted-foreground ml-2"
:class="isExpiredEpoch(oauth.expires_at) ? 'text-destructive' : ''"
>
到期: {{ formattedExpiresAt }}
</span>
</div>
<div
@@ -259,20 +267,6 @@
</Button>
</div>
</div>
<div
v-if="oauth.step === 'completed'"
class="pt-2 border-t border-border/40 text-xs text-muted-foreground space-y-1"
>
<div class="flex items-center justify-between gap-2">
<span>expires_at</span>
<span class="font-mono">{{ formattedExpiresAt }}</span>
</div>
<div class="flex items-center justify-between gap-2">
<span>refresh token</span>
<span>{{ oauth.has_refresh_token ? '有' : '无' }}</span>
</div>
</div>
</div>
</div>
@@ -882,6 +876,13 @@ function loadKeyData() {
model_include_patterns_text: (props.editingKey.model_include_patterns || []).join(', '),
model_exclude_patterns_text: (props.editingKey.model_exclude_patterns || []).join(', ')
}
// 如果是 OAuth 类型且有 token(有 oauth_expires_at),初始化 OAuth 状态为 completed
if (props.editingKey.auth_type === 'oauth' && props.editingKey.oauth_expires_at != null) {
oauth.value.step = 'completed'
oauth.value.expires_at = props.editingKey.oauth_expires_at
oauth.value.has_refresh_token = null // 后端不返回此字段,设为未知
}
}
// 使用 useFormDialog 统一处理对话框逻辑
+13 -1
View File
@@ -628,7 +628,17 @@ def _build_key_response(
key_dict = key.__dict__.copy()
key_dict.pop("_sa_instance_state", None)
key_dict.pop("api_key", None) # 移除敏感字段,避免泄露
key_dict.pop("auth_config", None) # 移除敏感字段,避免泄露
# 提取 OAuth expires_at(如果是 OAuth 类型)
oauth_expires_at = None
encrypted_auth_config = key_dict.pop("auth_config", None) # 移除敏感字段,避免泄露
if auth_type == "oauth" and encrypted_auth_config:
try:
decrypted_config = crypto_service.decrypt(encrypted_auth_config)
auth_config = json.loads(decrypted_config)
oauth_expires_at = auth_config.get("expires_at")
except Exception:
pass
# 从 health_by_format 计算汇总字段(便于列表展示)
health_by_format = key.health_by_format or {}
@@ -673,6 +683,8 @@ def _build_key_response(
"consecutive_failures": max_consecutive,
"last_failure_at": last_failure,
"circuit_breaker_open": any_circuit_open,
# OAuth 相关
"oauth_expires_at": oauth_expires_at,
}
)
+3
View File
@@ -513,6 +513,9 @@ class EndpointAPIKeyResponse(BaseModel):
allowed_models: list[str] | None = None
capabilities: dict[str, bool] | None = Field(default=None, description="Key 能力标签")
# OAuth 相关
oauth_expires_at: int | None = Field(default=None, description="OAuth Token 过期时间(Unix 时间戳)")
# 缓存与熔断配置
cache_ttl_minutes: int = Field(default=5, description="缓存 TTL(分钟),0=禁用")
max_probe_interval_minutes: int = Field(default=32, description="熔断探测间隔(分钟)")