feat(dashboard): add model/status/account/api-key filters to usage details tab
- Add apiKey column to requestDetails (schema v2 + migration 002) - Persist masked API key via buildRequestDetail across chatCore handlers - Add getRequestDetails apiKey filter + distinct models/apiKeys/statuses helpers - New /api/usage/filters endpoint returning grouped connections per provider - Group account dropdown by provider using <optgroup>; add Status column with success/error badge to the details table Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
@@ -538,6 +538,7 @@ export async function handleChatCore({
|
||||
provider,
|
||||
model,
|
||||
connectionId,
|
||||
apiKey,
|
||||
latency: { ttft: 0, total: Date.now() - requestStartTime },
|
||||
tokens: { prompt_tokens: 0, completion_tokens: 0 },
|
||||
request: extractRequestConfig(body, stream),
|
||||
@@ -676,6 +677,7 @@ export async function handleChatCore({
|
||||
provider,
|
||||
model,
|
||||
connectionId,
|
||||
apiKey,
|
||||
latency: { ttft: 0, total: Date.now() - requestStartTime },
|
||||
tokens: { prompt_tokens: 0, completion_tokens: 0 },
|
||||
request: extractRequestConfig(body, stream),
|
||||
|
||||
@@ -286,7 +286,7 @@ export async function handleNonStreamingResponse({ providerResponse, provider, m
|
||||
|
||||
const totalLatency = Date.now() - requestStartTime;
|
||||
saveRequestDetail(buildRequestDetail({
|
||||
provider, model, connectionId,
|
||||
provider, model, connectionId, apiKey,
|
||||
latency: { ttft: totalLatency, total: totalLatency },
|
||||
tokens: usage || { prompt_tokens: 0, completion_tokens: 0 },
|
||||
request: extractRequestConfig(body, stream),
|
||||
|
||||
@@ -57,11 +57,21 @@ export function extractUsageFromResponse(responseBody) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Mask API keys before they reach the requestDetails data blob / DB column.
|
||||
// Only the prefix is kept — enough to distinguish keys without leaking them.
|
||||
export function maskApiKey(key) {
|
||||
if (!key || typeof key !== "string") return undefined;
|
||||
const trimmed = key.trim();
|
||||
if (trimmed.length <= 8) return trimmed.charAt(0) + "***";
|
||||
return trimmed.slice(0, 8) + "***";
|
||||
}
|
||||
|
||||
export function buildRequestDetail(base, overrides = {}) {
|
||||
return {
|
||||
provider: base.provider || "unknown",
|
||||
model: base.model || "unknown",
|
||||
connectionId: base.connectionId || undefined,
|
||||
apiKey: maskApiKey(base.apiKey),
|
||||
timestamp: new Date().toISOString(),
|
||||
latency: base.latency || { ttft: 0, total: 0 },
|
||||
tokens: base.tokens || { prompt_tokens: 0, completion_tokens: 0 },
|
||||
|
||||
@@ -230,6 +230,7 @@ export async function handleStreamingResponse({
|
||||
provider,
|
||||
model,
|
||||
connectionId,
|
||||
apiKey,
|
||||
latency: { ttft: 0, total: Date.now() - requestStartTime },
|
||||
tokens: { prompt_tokens: 0, completion_tokens: 0 },
|
||||
request: extractRequestConfig(body, stream),
|
||||
@@ -293,6 +294,7 @@ export function buildOnStreamComplete({
|
||||
provider,
|
||||
model,
|
||||
connectionId,
|
||||
apiKey,
|
||||
latency,
|
||||
tokens: usage || { prompt_tokens: 0, completion_tokens: 0 },
|
||||
request: extractRequestConfig(body, stream),
|
||||
|
||||
Reference in New Issue
Block a user