mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
fix(ui): correct account subscription and quota displays
Preserve authentication snapshots for diagnostic account badges and align account note typography. Hide unavailable dashboard quotas consistently and show available quota rates independently of local request counts. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -965,6 +965,7 @@ pub struct DiagnosticObservation {
|
||||
pub key: String,
|
||||
pub name: String,
|
||||
pub account_provider_kind: Option<String>,
|
||||
pub account_authentication_kind: Option<String>,
|
||||
pub account_plan_type: Option<String>,
|
||||
pub request_count: u64,
|
||||
pub success_count: u64,
|
||||
@@ -1435,6 +1436,7 @@ pub struct UsageInsight {
|
||||
pub struct DiagnosticsItem {
|
||||
pub key: String,
|
||||
pub name: String,
|
||||
pub account_authentication_kind: Option<String>,
|
||||
pub account_plan_type: Option<String>,
|
||||
pub account_plan_type_display: Option<String>,
|
||||
pub request_count: u64,
|
||||
|
||||
@@ -344,6 +344,7 @@ impl ObservabilityService for DefaultObservabilityService {
|
||||
DiagnosticsItem {
|
||||
key: item.key,
|
||||
name: item.name,
|
||||
account_authentication_kind: item.account_authentication_kind,
|
||||
account_plan_type_display: item.account_provider_kind.as_deref().and_then(
|
||||
|provider| {
|
||||
self.providers
|
||||
|
||||
@@ -1559,6 +1559,7 @@ fn diagnostic(name: &str, request_count: u64) -> DiagnosticObservation {
|
||||
retry_count: 0,
|
||||
retried_request_count: 0,
|
||||
account_provider_kind: None,
|
||||
account_authentication_kind: None,
|
||||
account_plan_type: None,
|
||||
key: name.to_owned(),
|
||||
name: name.to_owned(),
|
||||
|
||||
@@ -1339,6 +1339,7 @@ pub(crate) fn diagnostics_view(
|
||||
.into_iter()
|
||||
.map(|item| DiagnosticItemView {
|
||||
key: item.key,
|
||||
authentication_kind: item.account_authentication_kind,
|
||||
name: if item.name == "__none__" {
|
||||
"未知".to_owned()
|
||||
} else {
|
||||
|
||||
@@ -845,6 +845,7 @@ pub struct UsageCacheView {
|
||||
pub struct DiagnosticItemView {
|
||||
pub key: String,
|
||||
pub name: String,
|
||||
pub authentication_kind: Option<String>,
|
||||
pub account_plan_type: Option<String>,
|
||||
pub account_plan_type_display: Option<String>,
|
||||
pub request_count: u64,
|
||||
|
||||
@@ -670,67 +670,84 @@ async fn diagnostics_should_keep_stable_key_and_display_name_contract() {
|
||||
|
||||
use crate::admin::{AdminTestFixture, AdminTestState};
|
||||
|
||||
let fixture = AdminTestFixture::new().await;
|
||||
fixture.auth.insert_session("valid-session");
|
||||
*fixture.diagnostics.lock().expect("diagnostics") = DiagnosticsObservation {
|
||||
analytics: Default::default(),
|
||||
total_request_count: 4,
|
||||
items: vec![DiagnosticObservation {
|
||||
key: "acct_diag".to_owned(),
|
||||
account_provider_kind: Some("openai".to_owned()),
|
||||
account_plan_type: Some("pro".to_owned()),
|
||||
name: "diag@example.invalid".to_owned(),
|
||||
request_count: 2,
|
||||
success_count: 2,
|
||||
failure_count: 0,
|
||||
attempt_count: 5,
|
||||
total_tokens: 200,
|
||||
average_latency_ms: Some(100),
|
||||
latency_p95_ms: Some(3800),
|
||||
first_token_p95_ms: Some(1200),
|
||||
non_completion_count: 0,
|
||||
retry_count: 3,
|
||||
retried_request_count: 1,
|
||||
cost_coverage: CostCoverage::default(),
|
||||
costs: Vec::new(),
|
||||
}],
|
||||
};
|
||||
let response = observability::router::<AdminTestState>()
|
||||
.with_state(fixture.state())
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/admin/usage/insights/diagnostics?dimension=account")
|
||||
.header(header::COOKIE, "cpr_session=valid-session")
|
||||
.header("x-request-id", "req_diagnostics_snapshot")
|
||||
.body(Body::empty())
|
||||
.expect("diagnostics request"),
|
||||
)
|
||||
.await
|
||||
.expect("diagnostics response");
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = to_bytes(response.into_body(), 1024 * 1024)
|
||||
.await
|
||||
.expect("diagnostics body");
|
||||
let value: serde_json::Value = serde_json::from_slice(&body).expect("diagnostics JSON");
|
||||
assert_eq!(value["data"]["items"][0]["requestShare"], 0.5);
|
||||
assert_eq!(value["data"]["items"][0]["retryCount"], 3);
|
||||
assert_eq!(value["data"]["items"][0]["retryRate"], 0.5);
|
||||
assert_eq!(value["data"]["items"][0]["accountPlanType"], "pro");
|
||||
assert_eq!(value["data"]["items"][0]["accountPlanTypeDisplay"], "Pro");
|
||||
assert_eq!(
|
||||
(
|
||||
&value["data"]["items"][0]["key"],
|
||||
&value["data"]["items"][0]["name"],
|
||||
&value["data"]["items"][0]["latencyP95Ms"],
|
||||
&value["data"]["dimension"],
|
||||
),
|
||||
(
|
||||
&serde_json::json!("acct_diag"),
|
||||
&serde_json::json!("diag@example.invalid"),
|
||||
&serde_json::json!(3800),
|
||||
&serde_json::json!("account"),
|
||||
)
|
||||
);
|
||||
for (authentication_kind, plan_type, plan_display) in [
|
||||
(Some("oauth"), Some("pro"), Some("Pro")),
|
||||
(Some("api_key"), None, None),
|
||||
(None, None, None),
|
||||
] {
|
||||
let fixture = AdminTestFixture::new().await;
|
||||
fixture.auth.insert_session("valid-session");
|
||||
*fixture.diagnostics.lock().expect("diagnostics") = DiagnosticsObservation {
|
||||
analytics: Default::default(),
|
||||
total_request_count: 4,
|
||||
items: vec![DiagnosticObservation {
|
||||
key: "acct_diag".to_owned(),
|
||||
account_provider_kind: Some("openai".to_owned()),
|
||||
account_authentication_kind: authentication_kind.map(str::to_owned),
|
||||
account_plan_type: plan_type.map(str::to_owned),
|
||||
name: "diag@example.invalid".to_owned(),
|
||||
request_count: 2,
|
||||
success_count: 2,
|
||||
failure_count: 0,
|
||||
attempt_count: 5,
|
||||
total_tokens: 200,
|
||||
average_latency_ms: Some(100),
|
||||
latency_p95_ms: Some(3800),
|
||||
first_token_p95_ms: Some(1200),
|
||||
non_completion_count: 0,
|
||||
retry_count: 3,
|
||||
retried_request_count: 1,
|
||||
cost_coverage: CostCoverage::default(),
|
||||
costs: Vec::new(),
|
||||
}],
|
||||
};
|
||||
let response = observability::router::<AdminTestState>()
|
||||
.with_state(fixture.state())
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/admin/usage/insights/diagnostics?dimension=account")
|
||||
.header(header::COOKIE, "cpr_session=valid-session")
|
||||
.header("x-request-id", "req_diagnostics_snapshot")
|
||||
.body(Body::empty())
|
||||
.expect("diagnostics request"),
|
||||
)
|
||||
.await
|
||||
.expect("diagnostics response");
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = to_bytes(response.into_body(), 1024 * 1024)
|
||||
.await
|
||||
.expect("diagnostics body");
|
||||
let value: serde_json::Value = serde_json::from_slice(&body).expect("diagnostics JSON");
|
||||
assert_eq!(value["data"]["items"][0]["requestShare"], 0.5);
|
||||
assert_eq!(value["data"]["items"][0]["retryCount"], 3);
|
||||
assert_eq!(value["data"]["items"][0]["retryRate"], 0.5);
|
||||
assert_eq!(
|
||||
value["data"]["items"][0]["authenticationKind"],
|
||||
serde_json::json!(authentication_kind)
|
||||
);
|
||||
assert_eq!(
|
||||
value["data"]["items"][0]["accountPlanType"],
|
||||
serde_json::json!(plan_type)
|
||||
);
|
||||
assert_eq!(
|
||||
value["data"]["items"][0]["accountPlanTypeDisplay"],
|
||||
serde_json::json!(plan_display)
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
&value["data"]["items"][0]["key"],
|
||||
&value["data"]["items"][0]["name"],
|
||||
&value["data"]["items"][0]["latencyP95Ms"],
|
||||
&value["data"]["dimension"],
|
||||
),
|
||||
(
|
||||
&serde_json::json!("acct_diag"),
|
||||
&serde_json::json!("diag@example.invalid"),
|
||||
&serde_json::json!(3800),
|
||||
&serde_json::json!("account"),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn usage_record_with_account(
|
||||
|
||||
@@ -634,6 +634,7 @@ impl<'a> AnalyticsSnapshot<'a> {
|
||||
key: key.clone(),
|
||||
name: key,
|
||||
account_provider_kind: None,
|
||||
account_authentication_kind: None,
|
||||
account_plan_type: None,
|
||||
request_count: metrics.request_count,
|
||||
success_count: metrics.success_count,
|
||||
@@ -669,9 +670,9 @@ impl<'a> AnalyticsSnapshot<'a> {
|
||||
.map(|item| item.key.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let statement = if dimension == DiagnosticDimension::Account {
|
||||
"select requested.id, historical.provider_kind, historical.provider_account_plan_type_snapshot as plan_type, coalesce(historical.provider_account_email_snapshot,historical.provider_account_name_snapshot,requested.id) as name from unnest($1::text[]) requested(id) left join lateral (select provider_kind,provider_account_plan_type_snapshot,provider_account_email_snapshot,provider_account_name_snapshot from model_request_observations mr where mr.provider_account_ref=requested.id order by mr.started_at desc,mr.id desc limit 1) historical on true"
|
||||
"select requested.id, historical.provider_kind, historical.provider_account_authentication_kind_snapshot as authentication_kind, historical.provider_account_plan_type_snapshot as plan_type, coalesce(historical.provider_account_email_snapshot,historical.provider_account_name_snapshot,requested.id) as name from unnest($1::text[]) requested(id) left join lateral (select provider_kind,provider_account_authentication_kind_snapshot,provider_account_plan_type_snapshot,provider_account_email_snapshot,provider_account_name_snapshot from model_request_observations mr where mr.provider_account_ref=requested.id order by mr.started_at desc,mr.id desc limit 1) historical on true"
|
||||
} else {
|
||||
"select requested.id, null::text as provider_kind, null::text as plan_type,coalesce(historical.client_api_key_name_snapshot,requested.id) as name from unnest($1::text[]) requested(id) left join lateral (select client_api_key_name_snapshot from model_request_observations mr where mr.client_api_key_ref=requested.id order by mr.started_at desc,mr.id desc limit 1) historical on true"
|
||||
"select requested.id, null::text as provider_kind, null::text as authentication_kind, null::text as plan_type,coalesce(historical.client_api_key_name_snapshot,requested.id) as name from unnest($1::text[]) requested(id) left join lateral (select client_api_key_name_snapshot from model_request_observations mr where mr.client_api_key_ref=requested.id order by mr.started_at desc,mr.id desc limit 1) historical on true"
|
||||
};
|
||||
let labels = sqlx::query(statement)
|
||||
.bind(ids)
|
||||
@@ -683,6 +684,7 @@ impl<'a> AnalyticsSnapshot<'a> {
|
||||
if let Some(item) = observations.iter_mut().find(|item| item.key == id) {
|
||||
item.name = get(&row, "name")?;
|
||||
item.account_provider_kind = get(&row, "provider_kind")?;
|
||||
item.account_authentication_kind = get(&row, "authentication_kind")?;
|
||||
item.account_plan_type = get(&row, "plan_type")?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +279,7 @@ fn admin_diagnostic_observation(
|
||||
key: observation.key,
|
||||
name: observation.name,
|
||||
account_provider_kind: observation.account_provider_kind,
|
||||
account_authentication_kind: observation.account_authentication_kind,
|
||||
account_plan_type: observation.account_plan_type,
|
||||
request_count: observation.request_count,
|
||||
success_count: observation.success_count,
|
||||
|
||||
@@ -378,6 +378,7 @@ pub struct DiagnosticObservation {
|
||||
pub key: String,
|
||||
pub name: String,
|
||||
pub account_provider_kind: Option<String>,
|
||||
pub account_authentication_kind: Option<String>,
|
||||
pub account_plan_type: Option<String>,
|
||||
pub request_count: u64,
|
||||
pub success_count: u64,
|
||||
|
||||
@@ -497,6 +497,44 @@ async fn usage_list_and_diagnostics_preserve_account_snapshots_by_id() {
|
||||
diagnostics.items[0].account_plan_type.as_deref(),
|
||||
Some("team")
|
||||
);
|
||||
for (authentication_kind, plan_type) in [
|
||||
(Some("api_key"), None),
|
||||
(Some("oauth"), Some("team")),
|
||||
(None, None),
|
||||
] {
|
||||
sqlx::query(
|
||||
"update model_requests set request_observation_json = jsonb_set(
|
||||
request_observation_json, '{account}',
|
||||
(request_observation_json->'account') || jsonb_build_object(
|
||||
'authenticationKind', $1::text, 'planTypeSnapshot', $2::text
|
||||
)
|
||||
) where id = 'req_observe_uncommitted'",
|
||||
)
|
||||
.bind(authentication_kind)
|
||||
.bind(plan_type)
|
||||
.execute(&database.pool)
|
||||
.await
|
||||
.expect("update request authentication snapshot");
|
||||
super::drain_analytics(&database.pool).await;
|
||||
let diagnostics = store
|
||||
.usage_diagnostics(
|
||||
range,
|
||||
admin_observability::UsageFilter {
|
||||
provider_account_ref: Some("acct_team".to_owned()),
|
||||
..Default::default()
|
||||
},
|
||||
admin_observability::DiagnosticDimension::Account,
|
||||
100,
|
||||
)
|
||||
.await
|
||||
.expect("deleted account authentication snapshot");
|
||||
assert_eq!(diagnostics.items.len(), 1);
|
||||
assert_eq!(
|
||||
diagnostics.items[0].account_authentication_kind.as_deref(),
|
||||
authentication_kind
|
||||
);
|
||||
assert_eq!(diagnostics.items[0].account_plan_type.as_deref(), plan_type);
|
||||
}
|
||||
database.close().await;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -2250,7 +2250,7 @@ Dashboard 返回原始 `asOf` 及 `asOfDisplay`,作为当前数据的查询锚
|
||||
`retryCount` 为额外执行尝试次数之和,`retryRate` 为发生过重试的请求数占该组请求数的比例,同一请求多次重试只计一次
|
||||
|
||||
使用记录列表和错误排查列表的 `accountPlanType` / `accountPlanTypeDisplay` 来自记录保存的账号订阅快照。
|
||||
账号维度诊断使用该账号最近请求的订阅快照;未保存订阅时为 `null`
|
||||
账号维度诊断使用该账号最近请求的订阅快照,并通过 `authenticationKind` 返回同一请求的认证类型快照;未保存的字段为 `null`,非账号维度的认证类型为 `null`
|
||||
|
||||
管理端请求列表及 Dashboard 最近请求中的 `accountNotes` 来自请求保存的账号备注快照,未保存时为 `null`
|
||||
|
||||
|
||||
@@ -361,6 +361,7 @@ export type UsageMetricResponse<M extends keyof UsageMetricData> = {
|
||||
export interface UsageDiagnosticItem {
|
||||
key: string
|
||||
name: string
|
||||
authenticationKind: string | null
|
||||
accountPlanType: string | null
|
||||
accountPlanTypeDisplay: string | null
|
||||
requestCount: number
|
||||
|
||||
@@ -79,9 +79,10 @@ const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
<template #accountPlanType="{ row }">
|
||||
<AccountPlanBadge
|
||||
v-if="row.accountPlanType"
|
||||
v-if="row.authenticationKind === 'api_key' || row.accountPlanType"
|
||||
:authentication-kind="row.authenticationKind ?? undefined"
|
||||
:plan-type="row.accountPlanType"
|
||||
:plan-type-display="row.accountPlanTypeDisplay || row.accountPlanType"
|
||||
:plan-type-display="row.accountPlanTypeDisplay || row.accountPlanType || ''"
|
||||
size="sm"
|
||||
/>
|
||||
<span v-else class="text-cp-text-quaternary">—</span>
|
||||
|
||||
@@ -45,7 +45,7 @@ watch(() => props.notes, recheckOpen, { flush: 'post' })
|
||||
<template #reference>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex min-w-0 max-w-full cursor-default touch-manipulation items-center rounded-cp-sm border-0 bg-transparent p-0 text-left text-cp-xs leading-4 text-cp-text-tertiary outline-none focus-visible:ring-2 focus-visible:ring-cp-control-outline"
|
||||
class="inline-flex min-w-0 max-w-full cursor-default touch-manipulation items-center rounded-cp-sm border-0 bg-transparent p-0 text-left font-mono text-cp-xs leading-4 font-emphasis text-cp-text-quaternary outline-none focus-visible:ring-2 focus-visible:ring-cp-control-outline"
|
||||
:aria-label="t('accounts.notes.viewAccountNotes')"
|
||||
aria-haspopup="dialog"
|
||||
:aria-expanded="open"
|
||||
|
||||
@@ -37,7 +37,7 @@ const additionalEntryCount = computed(() => Math.max(summaryEntries.value.length
|
||||
|
||||
<template>
|
||||
<div class="box-border grid min-h-16.5 w-full min-w-0 content-center gap-1.5 py-1.5">
|
||||
<template v-if="account.authenticationKind === 'api_key'">
|
||||
<template v-if="account.authenticationKind === 'api_key' && summaryEntries.length === 0">
|
||||
<span
|
||||
class="flex min-w-0 items-baseline gap-1 font-mono leading-none tabular-nums"
|
||||
:title="t('accounts.quota.localTokens')"
|
||||
@@ -55,10 +55,11 @@ const additionalEntryCount = computed(() => Math.max(summaryEntries.value.length
|
||||
</template>
|
||||
<template v-else-if="summaryEntries.length > 0">
|
||||
<div
|
||||
v-if="hasUsage"
|
||||
v-if="hasUsage || currentUsageWindow"
|
||||
class="flex min-w-0 items-baseline justify-between gap-2 leading-none"
|
||||
>
|
||||
<span
|
||||
v-if="hasUsage"
|
||||
class="flex min-w-0 items-baseline gap-1 font-mono tabular-nums"
|
||||
:title="t('accounts.quota.totalTokensFor', { windowLabelDisplay: usageWindowLabels[account.usage.windowKind] })"
|
||||
>
|
||||
@@ -71,7 +72,7 @@ const additionalEntryCount = computed(() => Math.max(summaryEntries.value.length
|
||||
</span>
|
||||
<span
|
||||
v-if="currentUsageWindow"
|
||||
class="flex shrink-0 items-baseline gap-1 text-[9px] font-emphasis text-cp-text-quaternary"
|
||||
class="ml-auto flex shrink-0 items-baseline gap-1 text-[9px] font-emphasis text-cp-text-quaternary"
|
||||
:title="t('accounts.quota.usageRateHelp')"
|
||||
>
|
||||
<span>{{ t('accounts.quota.usageRate') }}</span>
|
||||
|
||||
@@ -315,7 +315,8 @@ const statusBars = computed(() => {
|
||||
</span>
|
||||
|
||||
<AccountUsageWindow
|
||||
:window="account.usageWindow ?? undefined"
|
||||
v-if="account.usageWindow"
|
||||
:window="account.usageWindow"
|
||||
:show-local-value="false"
|
||||
variant="metric"
|
||||
/>
|
||||
|
||||
@@ -200,9 +200,10 @@ function diagnosticNameDisplay(name: string) {
|
||||
|
||||
<template #accountPlanType="{ row }">
|
||||
<AccountPlanBadge
|
||||
v-if="row.accountPlanType"
|
||||
v-if="row.authenticationKind === 'api_key' || row.accountPlanType"
|
||||
:authentication-kind="row.authenticationKind ?? undefined"
|
||||
:plan-type="row.accountPlanType"
|
||||
:plan-type-display="row.accountPlanTypeDisplay || row.accountPlanType"
|
||||
:plan-type-display="row.accountPlanTypeDisplay || row.accountPlanType || ''"
|
||||
size="sm"
|
||||
/>
|
||||
<span v-else class="text-cp-text-quaternary">—</span>
|
||||
|
||||
Reference in New Issue
Block a user