diff --git a/open-sse/handlers/chatCore.js b/open-sse/handlers/chatCore.js
index 38cf1490..21255998 100644
--- a/open-sse/handlers/chatCore.js
+++ b/open-sse/handlers/chatCore.js
@@ -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),
diff --git a/open-sse/handlers/chatCore/nonStreamingHandler.js b/open-sse/handlers/chatCore/nonStreamingHandler.js
index f93058fd..d1f4a845 100644
--- a/open-sse/handlers/chatCore/nonStreamingHandler.js
+++ b/open-sse/handlers/chatCore/nonStreamingHandler.js
@@ -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),
diff --git a/open-sse/handlers/chatCore/requestDetail.js b/open-sse/handlers/chatCore/requestDetail.js
index b3d24115..de1b3cad 100644
--- a/open-sse/handlers/chatCore/requestDetail.js
+++ b/open-sse/handlers/chatCore/requestDetail.js
@@ -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 },
diff --git a/open-sse/handlers/chatCore/streamingHandler.js b/open-sse/handlers/chatCore/streamingHandler.js
index 15fdfd2f..d37582bd 100644
--- a/open-sse/handlers/chatCore/streamingHandler.js
+++ b/open-sse/handlers/chatCore/streamingHandler.js
@@ -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),
diff --git a/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js b/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js
index af8eb736..d3637df5 100644
--- a/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js
+++ b/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js
@@ -114,9 +114,19 @@ export default function RequestDetailsTab() {
const [providerNameCache, setProviderNameCache] = useState(null);
const [filters, setFilters] = useState({
provider: "",
+ model: "",
+ status: "",
+ connectionId: "",
+ apiKey: "",
startDate: "",
endDate: ""
});
+ const [filterOptions, setFilterOptions] = useState({
+ models: [],
+ apiKeys: [],
+ statuses: [],
+ connections: []
+ });
const fetchProviders = useCallback(async () => {
try {
@@ -131,6 +141,21 @@ export default function RequestDetailsTab() {
}
}, []);
+ const fetchFilterOptions = useCallback(async () => {
+ try {
+ const res = await fetch("/api/usage/filters");
+ const data = await res.json();
+ setFilterOptions({
+ models: data.models || [],
+ apiKeys: data.apiKeys || [],
+ statuses: data.statuses || [],
+ connections: data.connections || []
+ });
+ } catch (error) {
+ console.error("Failed to fetch filter options:", error);
+ }
+ }, []);
+
const fetchDetails = useCallback(async () => {
setLoading(true);
try {
@@ -139,6 +164,10 @@ export default function RequestDetailsTab() {
pageSize: pagination.pageSize.toString()
});
if (filters.provider) params.append("provider", filters.provider);
+ if (filters.model) params.append("model", filters.model);
+ if (filters.status) params.append("status", filters.status);
+ if (filters.connectionId) params.append("connectionId", filters.connectionId);
+ if (filters.apiKey) params.append("apiKey", filters.apiKey);
if (filters.startDate) params.append("startDate", filters.startDate);
if (filters.endDate) params.append("endDate", filters.endDate);
@@ -156,7 +185,8 @@ export default function RequestDetailsTab() {
useEffect(() => {
fetchProviders();
- }, [fetchProviders]);
+ fetchFilterOptions();
+ }, [fetchProviders, fetchFilterOptions]);
useEffect(() => {
fetchDetails();
@@ -176,7 +206,7 @@ export default function RequestDetailsTab() {
};
const handleClearFilters = () => {
- setFilters({ provider: "", startDate: "", endDate: "" });
+ setFilters({ provider: "", model: "", status: "", connectionId: "", apiKey: "", startDate: "", endDate: "" });
};
return (
@@ -204,6 +234,98 @@ export default function RequestDetailsTab() {
))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -238,7 +360,7 @@ export default function RequestDetailsTab() {