Merge branch 'v2' into feat/models-page

This commit is contained in:
tbphp committed 2026-08-06 23:21:40 +08:00
commit ff7d0eee26
15 files changed
+104 -82

No files matched your search

+6 -6
View File
@@ -284,10 +284,10 @@ func TestHTTPRoutesReturnMethodNotAllowedBeforePlaneAuthentication(t *testing.T)
}
for _, event := range []string{
"control_plane_auth_failed",
"data_plane_auth_failed",
"control_plane_mutation",
"data_plane_route_not_found",
"auth_failed",
"auth_failed",
"mutation",
"route_not_found",
} {
if strings.Contains(logs.String(), event) {
t.Fatalf("wrong-method requests emitted %q: %s", event, logs.String())
@@ -332,7 +332,7 @@ func TestHTTPRoutesValidateDynamicPathsBeforeDataAuthentication(t *testing.T) {
})
}
if strings.Contains(logs.String(), "data_plane_auth_failed") {
if strings.Contains(logs.String(), "auth_failed") {
t.Fatalf("invalid dynamic paths emitted data auth failure: %s", logs.String())
}
}
@@ -374,7 +374,7 @@ func TestHTTPRoutesPreserveResponsesAuthenticationBeforeLocalRejection(t *testin
)
}
}
if !strings.Contains(logs.String(), "data_plane_auth_failed") {
if !strings.Contains(logs.String(), "auth_failed") {
t.Fatalf("Responses local rejections skipped authentication log: %s", logs.String())
}
}
+10 -10
View File
@@ -56,7 +56,7 @@ func TestAuthenticateEventsCountLockedRequestsWithoutRepeatingTransition(
assertControlAuthEventTotals(
t,
decodeControlJSONLogs(t, logs.Bytes()),
"control_plane_auth_failed",
"auth_failed",
[]float64{1},
)
@@ -74,10 +74,10 @@ func TestAuthenticateEventsCountLockedRequestsWithoutRepeatingTransition(
assertControlAuthEventTotals(
t,
events,
"control_plane_auth_failed",
"auth_failed",
[]float64{1},
)
assertControlAuthEventCount(t, events, "control_plane_auth_locked", 1)
assertControlAuthEventCount(t, events, "auth_locked", 1)
now = now.Add(time.Minute)
stillLocked := serveAuthRequest(
@@ -98,18 +98,18 @@ func TestAuthenticateEventsCountLockedRequestsWithoutRepeatingTransition(
assertControlAuthEventTotals(
t,
events,
"control_plane_auth_failed",
"auth_failed",
[]float64{1, 6},
)
assertControlAuthEventCount(t, events, "control_plane_auth_locked", 1)
assertControlAuthEventCount(t, events, "auth_locked", 1)
authEvent := controlEventsNamed(events, "control_plane_auth_failed")[0]
authEvent := controlEventsNamed(events, "auth_failed")[0]
if authEvent["peer_ip"] != "192.0.2.60" ||
authEvent["level"] != "warning" ||
authEvent["msg"] != "[CONTROL] Authentication failed" {
t.Fatalf("auth event = %#v", authEvent)
}
lockEvent := controlEventsNamed(events, "control_plane_auth_locked")[0]
lockEvent := controlEventsNamed(events, "auth_locked")[0]
if lockEvent["peer_ip"] != "192.0.2.60" ||
lockEvent["retry_after_seconds"] != float64(authLockDuration/time.Second) ||
lockEvent["level"] != "warning" ||
@@ -180,10 +180,10 @@ func TestAuthenticateLockRequestCanEmitBothEventsWhenGateOpens(t *testing.T) {
assertControlAuthEventTotals(
t,
events,
"control_plane_auth_failed",
"auth_failed",
[]float64{1, 5},
)
assertControlAuthEventCount(t, events, "control_plane_auth_locked", 1)
assertControlAuthEventCount(t, events, "auth_locked", 1)
assertAuthStatus(
t,
@@ -202,7 +202,7 @@ func TestAuthenticateLockRequestCanEmitBothEventsWhenGateOpens(t *testing.T) {
assertControlAuthEventCount(
t,
decodeControlJSONLogs(t, logs.Bytes()),
"control_plane_auth_locked",
"auth_locked",
1,
)
}
+1 -1
View File
@@ -186,7 +186,7 @@ func TestModelPriceMutationAuditExcludesAuthFailureAndWrongMethod(t *testing.T)
wrongMethod := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/model-prices/%d", row.ID), nil)
engine.ServeHTTP(httptest.NewRecorder(), wrongMethod)
events := controlEventsNamed(decodeControlJSONLogs(t, logs.Bytes()), "control_plane_mutation")
events := controlEventsNamed(decodeControlJSONLogs(t, logs.Bytes()), "mutation")
if len(events) != 0 {
t.Fatalf("mutation events = %#v, want none", events)
}
@@ -281,7 +281,7 @@ func TestGroupMutationAuditExcludesInternalCalls(t *testing.T) {
events := controlEventsNamed(
decodeControlJSONLogs(t, logs.Bytes()),
"control_plane_mutation",
"mutation",
)
if len(events) != 0 {
t.Fatalf("mutation events = %#v, want none", events)
@@ -485,7 +485,7 @@ func TestAccessKeyMutationAudit(t *testing.T) {
events := controlEventsNamed(
decodeControlJSONLogs(t, logs.Bytes()),
"control_plane_mutation",
"mutation",
)
if len(events) != 2 {
t.Fatalf("mutation events = %#v, want two", events)
@@ -1000,9 +1000,9 @@ func TestControlSecurityEventFormatterSecretMatrix(t *testing.T) {
logText := output.String()
for _, required := range []string{
"control_plane_auth_failed",
"control_plane_auth_locked",
"control_plane_mutation",
"auth_failed",
"auth_locked",
"mutation",
"settings_update",
"settings:global",
"access_key_reveal",
@@ -1457,7 +1457,7 @@ func oneMutationAuditEvent(
t.Helper()
events := controlEventsNamed(
decodeControlJSONLogs(t, logs),
"control_plane_mutation",
"mutation",
)
if len(events) != 1 {
t.Fatalf("mutation events = %#v, want one", events)
+5 -5
View File
@@ -254,7 +254,7 @@ func TestAuditMutationRecordsExactlyOneOutcome(t *testing.T) {
}
events := controlEventsNamed(
decodeControlJSONLogs(t, logs.Bytes()),
"control_plane_mutation",
"mutation",
)
if len(events) != 1 {
t.Fatalf("mutation events = %#v, want one", events)
@@ -309,7 +309,7 @@ func TestAuditMutationUsesSuccessfulLocatorOverride(t *testing.T) {
events := controlEventsNamed(
decodeControlJSONLogs(t, logs.Bytes()),
"control_plane_mutation",
"mutation",
)
if len(events) != 1 || events[0]["resource_locator"] != "probe:42" {
t.Fatalf("mutation events = %#v, want probe:42", events)
@@ -357,7 +357,7 @@ func TestAuditMutationLogsPanicThenRethrowsToRecovery(t *testing.T) {
}
events := controlEventsNamed(
decodeControlJSONLogs(t, logs.Bytes()),
"control_plane_mutation",
"mutation",
)
if len(events) != 1 {
t.Fatalf("mutation events = %#v, want one", events)
@@ -475,7 +475,7 @@ func TestMutationAuditExcludesReadAndDiscoveryRoutes(t *testing.T) {
events := controlEventsNamed(
decodeControlJSONLogs(t, logs.Bytes()),
"control_plane_mutation",
"mutation",
)
if len(events) != 0 {
t.Fatalf("mutation events = %#v, want none", events)
@@ -496,7 +496,7 @@ func assertMutationEvent(
) {
t.Helper()
want := map[string]any{
"event": "control_plane_mutation",
"event": "mutation",
"peer_ip": peerIP,
"operation": operation,
"resource_type": resourceType,
+18 -18
View File
@@ -54,16 +54,16 @@ func TestGatewaySecurityEventsUseIndependentCounters(t *testing.T) {
)
events := decodeGatewayJSONLogs(t, logs.Bytes())
assertGatewayEventCount(t, events, "data_plane_auth_failed", 1)
assertGatewayEventCount(t, events, "data_plane_route_not_found", 1)
authEvent := gatewayEventsNamed(events, "data_plane_auth_failed")[0]
assertGatewayEventCount(t, events, "auth_failed", 1)
assertGatewayEventCount(t, events, "route_not_found", 1)
authEvent := gatewayEventsNamed(events, "auth_failed")[0]
if authEvent["peer_ip"] != "192.0.2.10" ||
authEvent["total"] != float64(1) ||
authEvent["level"] != "warning" ||
authEvent["msg"] != "[DATA] Authentication failed" {
t.Fatalf("auth event = %#v", authEvent)
}
routeEvent := gatewayEventsNamed(events, "data_plane_route_not_found")[0]
routeEvent := gatewayEventsNamed(events, "route_not_found")[0]
if routeEvent["peer_ip"] != "192.0.2.10" ||
routeEvent["access_key_id"] != float64(1) ||
routeEvent["total"] != float64(1) ||
@@ -88,7 +88,7 @@ func TestGatewaySecurityEventCountersAccumulateAcrossWindows(t *testing.T) {
}{
{
name: "authentication",
eventName: "data_plane_auth_failed",
eventName: "auth_failed",
serve: func(engine http.Handler) {
serveGatewaySecurityRequest(
engine,
@@ -101,7 +101,7 @@ func TestGatewaySecurityEventCountersAccumulateAcrossWindows(t *testing.T) {
},
{
name: "route",
eventName: "data_plane_route_not_found",
eventName: "route_not_found",
prepare: func(handler *Handler) {
handler.dialects = dialect.NewSet()
},
@@ -183,7 +183,7 @@ func TestGatewayRouteNotFoundCoversRecognizedRouteWithoutDialect(t *testing.T) {
}
events := gatewayEventsNamed(
decodeGatewayJSONLogs(t, logs.Bytes()),
"data_plane_route_not_found",
"route_not_found",
)
if len(events) != 1 ||
events[0]["access_key_id"] != float64(1) {
@@ -214,8 +214,8 @@ func TestGatewayInvalidAccessKeyDoesNotEmitRouteEvent(t *testing.T) {
t.Fatalf("response = %d %s, want 401", recorder.Code, recorder.Body.String())
}
events := decodeGatewayJSONLogs(t, logs.Bytes())
assertGatewayEventCount(t, events, "data_plane_auth_failed", 1)
assertGatewayEventCount(t, events, "data_plane_route_not_found", 0)
assertGatewayEventCount(t, events, "auth_failed", 1)
assertGatewayEventCount(t, events, "route_not_found", 0)
}
func TestGatewayUnknownRoutesBypassDataPlaneAuthentication(t *testing.T) {
@@ -248,8 +248,8 @@ func TestGatewayUnknownRoutesBypassDataPlaneAuthentication(t *testing.T) {
}
events := decodeGatewayJSONLogs(t, logs.Bytes())
assertGatewayEventCount(t, events, "data_plane_auth_failed", 0)
assertGatewayEventCount(t, events, "data_plane_route_not_found", 0)
assertGatewayEventCount(t, events, "auth_failed", 0)
assertGatewayEventCount(t, events, "route_not_found", 0)
}
func TestGatewayRejectsInvalidGeminiRoutesBeforeAuthentication(t *testing.T) {
@@ -283,8 +283,8 @@ func TestGatewayRejectsInvalidGeminiRoutesBeforeAuthentication(t *testing.T) {
}
events := decodeGatewayJSONLogs(t, logs.Bytes())
assertGatewayEventCount(t, events, "data_plane_auth_failed", 0)
assertGatewayEventCount(t, events, "data_plane_route_not_found", 0)
assertGatewayEventCount(t, events, "auth_failed", 0)
assertGatewayEventCount(t, events, "route_not_found", 0)
}
func TestGatewayPreservesResponsesAuthenticationBeforeLocalRejection(t *testing.T) {
@@ -367,10 +367,10 @@ func TestGatewayPreservesResponsesAuthenticationBeforeLocalRejection(t *testing.
}
events := decodeGatewayJSONLogs(t, logs.Bytes())
if got := gatewayEventsNamed(events, "data_plane_auth_failed"); len(got) == 0 {
t.Fatalf("data_plane_auth_failed events = %#v, want unauthenticated Responses requests logged", events)
if got := gatewayEventsNamed(events, "auth_failed"); len(got) == 0 {
t.Fatalf("auth_failed events = %#v, want unauthenticated Responses requests logged", events)
}
assertGatewayEventCount(t, events, "data_plane_route_not_found", 0)
assertGatewayEventCount(t, events, "route_not_found", 0)
}
func TestGatewayMalformedPeerDoesNotChangeResponseOrLeakRawValue(t *testing.T) {
@@ -398,7 +398,7 @@ func TestGatewayMalformedPeerDoesNotChangeResponseOrLeakRawValue(t *testing.T) {
}
events := gatewayEventsNamed(
decodeGatewayJSONLogs(t, logs.Bytes()),
"data_plane_auth_failed",
"auth_failed",
)
if len(events) != 1 || events[0]["peer_ip"] != "" {
t.Fatalf("auth events = %#v, want empty peer_ip", events)
@@ -431,7 +431,7 @@ func TestGatewaySecurityEventsIgnoreForwardedPeerHeaders(t *testing.T) {
}
events := gatewayEventsNamed(
decodeGatewayJSONLogs(t, logs.Bytes()),
"data_plane_auth_failed",
"auth_failed",
)
if len(events) != 1 || events[0]["peer_ip"] != "192.0.2.15" {
t.Fatalf("auth events = %#v, want direct peer", events)
@@ -346,8 +346,8 @@ func assertGatewaySecretMatrixOutput(
) {
t.Helper()
for _, required := range []string{
"data_plane_auth_failed",
"data_plane_route_not_found",
"auth_failed",
"route_not_found",
"access_key_id",
} {
if !strings.Contains(logText, required) {
+1 -1
View File
@@ -123,7 +123,7 @@ func TestCompactTextFormatterOrdersRequestFieldsByPriority(t *testing.T) {
"kid": 1,
"out_tokens": 486,
"model": "gpt-5.6-luna",
"event": "data_plane_request_completed",
"event": "request_completed",
"cost_usd": "0.00496768",
"in_tokens": 123068,
"duration": "30.2s",
+3 -3
View File
@@ -38,7 +38,7 @@ func processLogEntries(t *testing.T, output []byte) []map[string]any {
if err := json.Unmarshal(line, &entry); err != nil {
t.Fatalf("decode log line %q: %v", line, err)
}
if entry["event"] == "data_plane_request_completed" {
if entry["event"] == "request_completed" {
entries = append(entries, entry)
}
}
@@ -73,7 +73,7 @@ func TestProjectProcessLogUsesFrozenPricingAndInputOutputTotals(t *testing.T) {
t.Fatalf("projection = %t/%s", ok, level)
}
want := logrus.Fields{
"event": "data_plane_request_completed",
"event": "request_completed",
"status": "error", "http": http.StatusBadGateway,
"proto": string(protocol.OpenAICompletions), "ak_id": uint(42),
"model": "client-model", "up_model": "upstream-model",
@@ -124,7 +124,7 @@ func TestProjectProcessLogOmitsDefaultAndZeroValueFields(t *testing.T) {
t.Fatalf("projection = %t/%s", ok, level)
}
want := logrus.Fields{
"event": "data_plane_request_completed",
"event": "request_completed",
"status": "success", "proto": "anthropic", "ak_id": uint(42),
"model": "client-model", "group": uint(7), "kid": uint(8),
"duration": "25ms",
+7 -4
View File
@@ -717,7 +717,7 @@ function costLabel(log: RequestLogItemDto): string {
.logs-list__inline {
display: flex;
align-items: center;
gap: 5px;
gap: 0;
}
.logs-list__tokens {
@@ -727,12 +727,15 @@ function costLabel(log: RequestLogItemDto): string {
}
.logs-list__model {
flex: 1 1 0;
flex: 0 1 auto;
}
.logs-list__reasoning {
flex: 0 1 auto;
max-width: 45%;
flex: 0 0 auto;
}
.logs-list__inline > .logs-list__hint {
margin-left: 5px;
}
.logs-list__token-line {
+20 -7
View File
@@ -24,7 +24,8 @@ import EmptyState from '@/components/ui/EmptyState.vue'
import InlineFeedback from '@/components/ui/InlineFeedback.vue'
import QueryFeedback from '@/components/ui/QueryFeedback.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
import { formatEstimatedCost, formatInteger, formatTokens } from '@/lib/format'
import { formatCacheHitRate } from '@/lib/cache-rate'
import { formatEstimatedCost, formatInteger, formatPercent, formatTokens } from '@/lib/format'
import MonitorSectionHeading from './MonitorSectionHeading.vue'
import {
@@ -84,6 +85,20 @@ function formatCost(aggregate: UsageAggregateDto): string {
return formatEstimatedCost(aggregate.estimated_cost_nano_usd, locale.value)
}
function inputTokens(aggregate: UsageAggregateDto): number {
return (
aggregate.uncached_input_tokens +
aggregate.cache_read_tokens +
aggregate.cache_write_5m_tokens +
aggregate.cache_write_1h_tokens +
aggregate.cache_write_unknown_tokens
)
}
function cacheRateLabel(aggregate: UsageAggregateDto): string {
return formatCacheHitRate(aggregate.cache_read_tokens, inputTokens(aggregate), locale.value)
}
function groupName(groupID: number): string {
if (groupID === 0) return t('monitor.usage.breakdown.unattributedGroup')
return (
@@ -369,17 +384,15 @@ defineExpose({ openFilters })
<span class="usage-cell-label">{{ t('monitor.usage.columns.quality') }}</span>
<span>
{{
t('monitor.usage.columns.qualityUsage', {
missing: formatInteger(row.usage_missing_count, locale),
partial: formatInteger(row.partial_count, locale),
t('monitor.usage.columns.qualitySuccess', {
rate: formatPercent(row.success_count, row.request_count, locale),
})
}}
</span>
<small>
{{
t('monitor.usage.columns.qualityPricing', {
unpriced: formatInteger(row.unpriced_request_count, locale),
partial: formatInteger(row.pricing_partial_count, locale),
t('monitor.usage.columns.qualityCache', {
rate: cacheRateLabel(row),
})
}}
</small>
+4 -4
View File
@@ -65,7 +65,7 @@ export function formatLogReasoning(
details.push(formatReasoningBudgetCompact(value.budget_tokens, locale))
}
}
return details.join(' / ')
return details.join('/')
}
export function formatLogReasoningBudget(value: string, locale: string): string {
@@ -83,9 +83,9 @@ function formatReasoningBudgetCompact(value: string, locale: string): string {
try {
const amount = BigInt(value)
if (amount < 1_000n) return formatSignedInteger(value, locale)
if (amount < 1_000_000n) return `${amount / 1_000n}K`
if (amount < 1_000_000_000n) return `${amount / 1_000_000n}M`
return `${amount / 1_000_000_000n}B`
if (amount < 1_000_000n) return `${amount / 1_000n}k`
if (amount < 1_000_000_000n) return `${amount / 1_000_000n}m`
return `${amount / 1_000_000_000n}b`
} catch {
return value
}
+7 -5
View File
@@ -335,6 +335,8 @@ export default {
estimatedCost: 'Estimated cost',
quality: 'Quality',
actions: 'Actions',
qualitySuccess: 'Success rate {rate}',
qualityCache: 'Cache hit rate {rate}',
qualityUsage: 'Missing {missing} · Partial {partial}',
qualityPricing: 'Unpriced {unpriced} · Partial {partial}',
qualityCompact:
@@ -685,8 +687,8 @@ export default {
reasoningEffort: 'Reasoning effort',
reasoningBudget: 'Reasoning budget',
reasoningBudgetValue: '{value} Tokens',
reasoningBudgetDynamic: 'Automatic',
reasoningBudgetDisabled: 'Off',
reasoningBudgetDynamic: 'auto',
reasoningBudgetDisabled: 'off',
reasoningNotSpecified: 'Not specified',
statusCode: 'Status code',
duration: 'Duration',
@@ -779,9 +781,9 @@ export default {
modelMapping: 'Client model {client} → upstream model {upstream}',
modelMappingLabel: 'View model mapping',
reasoning: {
compact: 'Reasoning {value}',
disabled: 'Reasoning off',
dynamic: 'automatic',
compact: '[{value}]',
disabled: '[off]',
dynamic: 'auto',
},
tokens: {
input: 'Input',
+7 -5
View File
@@ -336,6 +336,8 @@ export default {
estimatedCost: '推定コスト',
quality: '品質',
actions: '操作',
qualitySuccess: '成功率 {rate}',
qualityCache: 'キャッシュ率 {rate}',
qualityUsage: 'なし {missing} · 部分 {partial}',
qualityPricing: '未設定 {unpriced} · 一部 {partial}',
qualityCompact:
@@ -685,8 +687,8 @@ export default {
reasoningEffort: '思考レベル',
reasoningBudget: '思考予算',
reasoningBudgetValue: '{value} Tokens',
reasoningBudgetDynamic: '自動',
reasoningBudgetDisabled: 'オフ',
reasoningBudgetDynamic: 'auto',
reasoningBudgetDisabled: 'off',
reasoningNotSpecified: '未指定',
statusCode: 'ステータスコード',
duration: '所要時間',
@@ -778,9 +780,9 @@ export default {
modelMapping: 'クライアントモデル {client} → アップストリームモデル {upstream}',
modelMappingLabel: 'モデルマッピングを表示',
reasoning: {
compact: '思考 {value}',
disabled: '思考オフ',
dynamic: '自動',
compact: '[{value}]',
disabled: '[off]',
dynamic: 'auto',
},
tokens: {
input: '入力',
+7 -5
View File
@@ -323,6 +323,8 @@ export default {
estimatedCost: '预估成本',
quality: '质量',
actions: '操作',
qualitySuccess: '成功率 {rate}',
qualityCache: '缓存率 {rate}',
qualityUsage: '缺失 {missing} · 部分 {partial}',
qualityPricing: '未定价 {unpriced} · 部分定价 {partial}',
qualityCompact:
@@ -668,8 +670,8 @@ export default {
reasoningEffort: '思考级别',
reasoningBudget: '思考预算',
reasoningBudgetValue: '{value} Tokens',
reasoningBudgetDynamic: '自动',
reasoningBudgetDisabled: '关闭',
reasoningBudgetDynamic: 'auto',
reasoningBudgetDisabled: 'off',
reasoningNotSpecified: '未指定',
statusCode: '状态码',
duration: '耗时',
@@ -759,9 +761,9 @@ export default {
modelMapping: '客户端模型 {client} → 上游模型 {upstream}',
modelMappingLabel: '查看模型映射',
reasoning: {
compact: '思考 {value}',
disabled: '思考关闭',
dynamic: '自动',
compact: '[{value}]',
disabled: '[off]',
dynamic: 'auto',
},
tokens: {
input: '输入',