Skip to content

Commit 6da4392

Browse files
committed
new dash
1 parent a58ec7d commit 6da4392

File tree

20 files changed

+1053
-138
lines changed

20 files changed

+1053
-138
lines changed

app/analytics/page.tsx

Lines changed: 139 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@ import {
99
SecurityPanel,
1010
LoadingSkeleton,
1111
DashboardHeader,
12-
StatusBadge
12+
StatusBadge,
13+
OAuthOverview,
14+
OAuthSecurityPanel,
15+
OAuthClientActivity,
16+
TokenExpiration,
17+
GrantTypeDistribution,
18+
ToolUsagePanel
1319
} from "@/components/analytics"
1420
import {
1521
BarChart3,
1622
Globe,
17-
Users,
1823
Activity,
1924
Clock,
2025
AlertTriangle,
@@ -32,6 +37,65 @@ export default function AnalyticsPage() {
3237
p95ResponseTime: number
3338
errorRate: number
3439
}
40+
oauth?: {
41+
totalClients: number
42+
activeTokens: number
43+
tokenRefreshRate: number
44+
pkceAdoption: number
45+
clientGrowth?: string
46+
tokenGrowth?: string
47+
refreshGrowth?: string
48+
pkceGrowth?: string
49+
clients?: {
50+
name: string
51+
clientId: string
52+
tokenCount: number
53+
lastActivity: string
54+
grantType: string
55+
pkceEnabled: boolean
56+
}[]
57+
expiringTokens?: {
58+
clientName: string
59+
tokenCount: number
60+
hoursUntilExpiry: number
61+
}[]
62+
grantTypes?: {
63+
type: string
64+
count: number
65+
percentage: number
66+
}[]
67+
}
68+
oauthSecurity?: {
69+
totalEvents: number
70+
invalidClientAttempts: number
71+
invalidGrantAttempts: number
72+
unauthorizedScopes: number
73+
events?: {
74+
clientName: string
75+
clientId: string
76+
eventType: string
77+
severity: string
78+
count: number
79+
lastOccurred: string
80+
}[]
81+
}
82+
toolUsage?: {
83+
tools: {
84+
toolName: string
85+
mcpMethod: string
86+
usageCount: number
87+
uniqueUsers: number
88+
avgResponseTime?: number
89+
}[]
90+
geographic: {
91+
country: string
92+
city?: string
93+
count: number
94+
percentage: number
95+
}[]
96+
totalCalls: number
97+
activeUsers: number
98+
}
3599
topEndpoints?: { endpoint: string; count: number }[]
36100
geography?: { country: string; count: number }[]
37101
security?: {
@@ -104,10 +168,30 @@ export default function AnalyticsPage() {
104168

105169

106170
const getHealthStatus = () => {
107-
if (!data?.performance) return { status: "unknown", color: "bg-muted" }
108-
const { errorRate, avgResponseTime } = data.performance
109-
if (errorRate > 5 || avgResponseTime > 1000) return { status: "critical", color: "bg-destructive" }
110-
if (errorRate > 1 || avgResponseTime > 500) return { status: "warning", color: "bg-yellow-500" }
171+
if (!data?.performance && !data?.oauth) return { status: "unknown", color: "bg-muted" }
172+
173+
let criticalIssues = 0
174+
let warningIssues = 0
175+
176+
// Check performance health
177+
if (data?.performance) {
178+
const { errorRate, avgResponseTime } = data.performance
179+
if (errorRate > 5 || avgResponseTime > 1000) criticalIssues++
180+
else if (errorRate > 1 || avgResponseTime > 500) warningIssues++
181+
}
182+
183+
// Check OAuth security health
184+
if (data?.oauthSecurity) {
185+
const { invalidClientAttempts, invalidGrantAttempts, unauthorizedScopes } = data.oauthSecurity
186+
if (invalidClientAttempts > 10 || invalidGrantAttempts > 20) criticalIssues++
187+
else if (invalidClientAttempts > 5 || invalidGrantAttempts > 10 || unauthorizedScopes > 5) warningIssues++
188+
}
189+
190+
// Check PKCE adoption
191+
if (data?.oauth && data.oauth.pkceAdoption < 50) warningIssues++
192+
193+
if (criticalIssues > 0) return { status: "critical", color: "bg-destructive" }
194+
if (warningIssues > 0) return { status: "warning", color: "bg-yellow-500" }
111195
return { status: "healthy", color: "bg-green-500" }
112196
}
113197

@@ -193,108 +277,72 @@ export default function AnalyticsPage() {
193277

194278
<div className="container mx-auto px-6 py-8 max-w-7xl space-y-8">
195279

196-
{/* Performance Overview */}
197-
{data.performance && (
198-
<section aria-labelledby="performance-title" className="space-y-6">
199-
<h2 id="performance-title" className="sr-only">
200-
Performance Overview
201-
</h2>
202-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
203-
<MetricCard
204-
title="Total Requests"
205-
value={data.performance.totalRequests.toLocaleString()}
206-
icon={BarChart3}
207-
variant="primary"
208-
change="+12.5%"
209-
changeType="positive"
210-
subtitle="vs last period"
211-
/>
212-
<MetricCard
213-
title="Avg Response"
214-
value={`${data.performance.avgResponseTime}ms`}
215-
icon={Clock}
216-
variant="primary"
217-
change={data.performance.avgResponseTime < 200 ? "-8%" : "+15%"}
218-
changeType={data.performance.avgResponseTime < 200 ? "positive" : "negative"}
219-
subtitle="response time"
220-
/>
221-
<MetricCard
222-
title="P95 Response"
223-
value={`${data.performance.p95ResponseTime}ms`}
224-
icon={TrendingUp}
225-
variant="secondary"
226-
change="+3.2%"
227-
changeType="neutral"
228-
subtitle="95th percentile"
229-
/>
230-
<MetricCard
231-
title="Error Rate"
232-
value={`${data.performance.errorRate}%`}
233-
icon={data.performance.errorRate > 5 ? AlertTriangle : CheckCircle}
234-
variant="secondary"
235-
change={data.performance.errorRate > 5 ? "+2.1%" : "-0.5%"}
236-
changeType={data.performance.errorRate > 5 ? "negative" : "positive"}
237-
subtitle="error percentage"
238-
/>
239-
</div>
240-
</section>
280+
{/* OAuth Overview */}
281+
{data.oauth && (
282+
<OAuthOverview
283+
totalClients={data.oauth.totalClients}
284+
activeTokens={data.oauth.activeTokens}
285+
tokenRefreshRate={data.oauth.tokenRefreshRate}
286+
pkceAdoption={data.oauth.pkceAdoption}
287+
clientGrowth={data.oauth.clientGrowth}
288+
tokenGrowth={data.oauth.tokenGrowth}
289+
refreshGrowth={data.oauth.refreshGrowth}
290+
pkceGrowth={data.oauth.pkceGrowth}
291+
/>
241292
)}
242293

243294
{/* Main Content Grid */}
244295
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
245-
{/* Left Column - Endpoints & Geography */}
296+
{/* Left Column - OAuth Client Management */}
246297
<div className="space-y-8">
247-
{/* Top Endpoints */}
248-
{data.topEndpoints && data.topEndpoints.length > 0 && (
249-
<DataTable
250-
title="Popular Endpoints"
251-
icon={Server}
252-
data={data.topEndpoints.map(endpoint => ({
253-
primary: endpoint.endpoint,
254-
value: endpoint.count
255-
}))}
256-
emptyMessage="No endpoint data available"
298+
{/* OAuth Client Activity */}
299+
{data.oauth?.clients && data.oauth.clients.length > 0 && (
300+
<OAuthClientActivity
301+
clients={data.oauth.clients}
302+
title="Active OAuth Clients"
303+
maxItems={6}
257304
/>
258305
)}
259306

260-
{/* Geographic Distribution */}
261-
{data.geography && data.geography.length > 0 && (
262-
<DataTable
263-
title="Geographic Distribution"
264-
icon={Globe}
265-
data={data.geography.map(country => ({
266-
primary: country.country,
267-
value: country.count
268-
}))}
269-
emptyMessage="No geographic data available"
270-
/>
307+
{/* Token Expiration Tracking */}
308+
{data.oauth?.expiringTokens && data.oauth.expiringTokens.length > 0 && (
309+
<TokenExpiration expiringTokens={data.oauth.expiringTokens} />
310+
)}
311+
312+
{/* Grant Type Distribution */}
313+
{data.oauth?.grantTypes && data.oauth.grantTypes.length > 0 && (
314+
<GrantTypeDistribution grantTypes={data.oauth.grantTypes} />
271315
)}
272316
</div>
273317

274-
{/* Middle Column - Enterprise Analytics */}
318+
{/* Middle Column - Tool Usage & Geography */}
275319
<div className="space-y-8">
276-
277-
278-
{/* Tool Usage */}
279-
{data.enterprise?.toolUsage && data.enterprise.toolUsage.length > 0 && (
280-
<DataTable
281-
title="Popular Tools"
282-
icon={Activity}
283-
data={data.enterprise.toolUsage.map(tool => ({
284-
primary: tool.toolName,
285-
secondary: tool.mcpMethod,
286-
value: `${tool.usageCount} calls`,
287-
badge: `${tool.uniqueUsers} users`
288-
}))}
289-
emptyMessage="No tool usage data available"
290-
maxItems={4}
320+
{/* Enhanced Tool Usage Panel */}
321+
{data.toolUsage && (
322+
<ToolUsagePanel
323+
toolUsage={data.toolUsage.tools}
324+
geographicUsage={data.toolUsage.geographic}
325+
totalCalls={data.toolUsage.totalCalls}
326+
activeUsers={data.toolUsage.activeUsers}
291327
/>
292328
)}
293329
</div>
294330

295-
{/* Right Column - Security */}
331+
{/* Right Column - Security Focus */}
296332
<div className="space-y-8">
297-
{data.security && (
333+
{/* OAuth Security Panel */}
334+
{data.oauthSecurity && (
335+
<OAuthSecurityPanel
336+
totalEvents={data.oauthSecurity.totalEvents}
337+
oauthEvents={data.oauthSecurity.events}
338+
invalidClientAttempts={data.oauthSecurity.invalidClientAttempts}
339+
invalidGrantAttempts={data.oauthSecurity.invalidGrantAttempts}
340+
unauthorizedScopes={data.oauthSecurity.unauthorizedScopes}
341+
/>
342+
)}
343+
344+
{/* General Security Events (Fallback) */}
345+
{data.security && !data.oauthSecurity && (
298346
<SecurityPanel
299347
eventCount={data.security.eventCount || 0}
300348
byOrganization={data.security.byOrganization}

app/api/oauth/register/route.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
22
import type { NextRequest } from 'next/server';
33
import { prisma } from '@/app/prisma';
44
import { randomBytes } from 'crypto';
5+
import { analyticsDB } from '@/lib/analytics-db';
56

67
export async function POST(request: NextRequest) {
78
const body = await request.json();
@@ -33,6 +34,29 @@ export async function POST(request: NextRequest) {
3334
},
3435
});
3536

37+
// Log client registration analytics
38+
try {
39+
const ip = request.headers.get('x-forwarded-for')?.split(',')[0] ||
40+
request.headers.get('x-real-ip') ||
41+
'127.0.0.1';
42+
43+
await analyticsDB.logRequest({
44+
timestamp: new Date(),
45+
endpoint: '/api/oauth/register',
46+
method: request.method,
47+
statusCode: 200,
48+
responseTime: 0,
49+
clientId: newClient.id,
50+
ipAddress: ip,
51+
userAgent: request.headers.get('user-agent') || '',
52+
oauthGrantType: 'client_registration',
53+
tokenScopes: [],
54+
redirectUri: redirect_uris[0] // First redirect URI
55+
});
56+
} catch (analyticsError) {
57+
console.warn('Failed to log client registration analytics:', analyticsError);
58+
}
59+
3660
const response = NextResponse.json({
3761
client_id: newClient.clientId,
3862
client_secret: clientSecret, // This is the only time the secret is sent

0 commit comments

Comments
 (0)