Skip to content

Commit 856e14f

Browse files
committed
v0.5
1 parent ed2c5fa commit 856e14f

File tree

18 files changed

+1596
-103
lines changed

18 files changed

+1596
-103
lines changed

app/analytics/page.tsx

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@ import {
77
SecurityPanel,
88
LoadingSkeleton,
99
DashboardHeader,
10-
StatusBadge,
1110
OAuthOverview,
1211
OAuthSecurityPanel,
1312
OAuthClientActivity,
1413
TokenExpiration,
15-
GrantTypeDistribution,
16-
ToolUsagePanel
14+
ToolUsagePanel,
15+
GrantTypeChart
1716
} from "@/components/analytics"
1817
import {
1918
BarChart3,
@@ -31,21 +30,25 @@ export default function AnalyticsPage() {
3130
errorRate: number
3231
}
3332
oauth?: {
33+
totalUsers: number
34+
activeUsers: number
3435
totalClients: number
36+
activeClients: number
3537
activeTokens: number
38+
recentAuthorizations: number
3639
tokenRefreshRate: number
3740
pkceAdoption: number
38-
clientGrowth?: string
39-
tokenGrowth?: string
40-
refreshGrowth?: string
41-
pkceGrowth?: string
41+
userActivity?: string
42+
clientActivity?: string
4243
clients?: {
4344
name: string
4445
clientId: string
45-
tokenCount: number
46+
uniqueUsers: number
47+
activeTokens: number
48+
recentRequests: number
4649
lastActivity: string
47-
grantType: string
48-
pkceEnabled: boolean
50+
userNames: string
51+
status: string
4952
}[]
5053
expiringTokens?: {
5154
clientName: string
@@ -125,9 +128,12 @@ export default function AnalyticsPage() {
125128
const fetchAnalytics = useCallback(async () => {
126129
try {
127130
setLoading(true)
128-
const response = await fetch(`/api/analytics?hours=${timeRange}`)
131+
const response = await fetch(`/api/analytics?hours=${timeRange}`, {
132+
credentials: 'include'
133+
})
129134
if (!response.ok) {
130-
throw new Error("Failed to fetch analytics")
135+
const errorData = await response.json()
136+
throw new Error(errorData.error || "Failed to fetch analytics")
131137
}
132138
const analyticsData = await response.json()
133139
setData(analyticsData)
@@ -237,7 +243,6 @@ export default function AnalyticsPage() {
237243
serverName={data.enterprise?.usersByMCPServer?.[0]?.mcpServerName}
238244
serverUrl={data.enterprise?.usersByMCPServer?.[0]?.mcpServerIdentifier}
239245
>
240-
<StatusBadge status={healthStatus.status as "healthy" | "warning" | "critical"} />
241246
<label htmlFor="time-range-select" className="sr-only">
242247
Time Range
243248
</label>
@@ -273,14 +278,15 @@ export default function AnalyticsPage() {
273278
{/* OAuth Overview */}
274279
{data.oauth && (
275280
<OAuthOverview
281+
totalUsers={data.oauth.totalUsers}
282+
activeUsers={data.oauth.activeUsers}
276283
totalClients={data.oauth.totalClients}
277284
activeTokens={data.oauth.activeTokens}
285+
recentAuthorizations={data.oauth.recentAuthorizations}
278286
tokenRefreshRate={data.oauth.tokenRefreshRate}
279287
pkceAdoption={data.oauth.pkceAdoption}
280-
clientGrowth={data.oauth.clientGrowth}
281-
tokenGrowth={data.oauth.tokenGrowth}
282-
refreshGrowth={data.oauth.refreshGrowth}
283-
pkceGrowth={data.oauth.pkceGrowth}
288+
userActivity={data.oauth.userActivity}
289+
clientActivity={data.oauth.clientActivity}
284290
/>
285291
)}
286292

@@ -292,7 +298,7 @@ export default function AnalyticsPage() {
292298
{data.oauth?.clients && data.oauth.clients.length > 0 && (
293299
<OAuthClientActivity
294300
clients={data.oauth.clients}
295-
title="Active OAuth Clients"
301+
title="Client-User Relationships"
296302
maxItems={6}
297303
/>
298304
)}
@@ -302,9 +308,9 @@ export default function AnalyticsPage() {
302308
<TokenExpiration expiringTokens={data.oauth.expiringTokens} />
303309
)}
304310

305-
{/* Grant Type Distribution */}
311+
{/* Grant Type Distribution - Pie Chart */}
306312
{data.oauth?.grantTypes && data.oauth.grantTypes.length > 0 && (
307-
<GrantTypeDistribution grantTypes={data.oauth.grantTypes} />
313+
<GrantTypeChart grantTypes={data.oauth.grantTypes} />
308314
)}
309315
</div>
310316

app/api/analytics/route.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { auth } from '@/app/auth';
44

55
export async function GET(request: NextRequest) {
66
try {
7+
// console.log('Request:', request);
78
// Authentication check
89
const session = await auth();
910
if (!session) {
@@ -14,7 +15,7 @@ export async function GET(request: NextRequest) {
1415
const { searchParams } = new URL(request.url);
1516
const hours = parseInt(searchParams.get('hours') || '24');
1617

17-
return getEnhancedAnalytics(hours);
18+
return await getEnhancedAnalytics(hours);
1819
} catch (error) {
1920
console.error('Analytics API error:', error);
2021
return NextResponse.json(
@@ -41,7 +42,13 @@ async function getEnhancedAnalytics(hours = 24) {
4142
usersByMCP,
4243
securityByOrg,
4344
privilegeEscalations,
44-
toolUsage
45+
toolUsage,
46+
toolGeography,
47+
oauthMetrics,
48+
oauthClientActivity,
49+
expiringTokens,
50+
grantTypeDistribution,
51+
oauthSecurityEvents
4552
] = await Promise.all([
4653
analyticsDB.getPerformanceMetrics(hours),
4754
analyticsDB.getTopEndpoints(hours, 10),
@@ -50,13 +57,32 @@ async function getEnhancedAnalytics(hours = 24) {
5057
analyticsDB.getUsersByMCPServer(hours),
5158
analyticsDB.getSecurityEventsByOrganization(hours),
5259
analyticsDB.getUserPrivilegeEscalations(168), // Last 7 days
53-
analyticsDB.getMCPToolUsage(hours)
60+
analyticsDB.getMCPToolUsage(hours),
61+
analyticsDB.getToolGeographyStats(hours),
62+
analyticsDB.getOAuthMetrics(hours),
63+
analyticsDB.getOAuthClientActivity(hours, 6),
64+
analyticsDB.getExpiringTokens(24),
65+
analyticsDB.getGrantTypeDistribution(hours),
66+
analyticsDB.getOAuthSecurityEvents(hours)
5467
]);
5568

5669
const data = {
5770
performance,
5871
topEndpoints: endpoints,
5972
geography,
73+
oauth: {
74+
...oauthMetrics,
75+
clients: oauthClientActivity,
76+
expiringTokens,
77+
grantTypes: grantTypeDistribution
78+
},
79+
oauthSecurity: oauthSecurityEvents,
80+
toolUsage: {
81+
tools: toolUsage,
82+
geographic: toolGeography,
83+
totalCalls: toolUsage.reduce((sum, tool) => sum + tool.usageCount, 0),
84+
activeUsers: toolUsage.reduce((sum, tool) => sum + tool.uniqueUsers, 0)
85+
},
6086
security: {
6187
events: securityEvents,
6288
eventCount: securityEvents.length,

app/mcp/[transport]/route.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ async function logEnhancedAnalytics(
180180
toolName = params.name;
181181
}
182182
}
183+
console.log('[MCP Analytics] Extracted:', { mcpMethod, toolName, hasParams: !!requestBody.params });
183184
}
184185

185186
// Get or create MCP server registration
@@ -255,8 +256,8 @@ async function logEnhancedAnalytics(
255256
'Content-Type': 'application/json',
256257
},
257258
body: JSON.stringify(analyticsData)
258-
}).catch(() => {
259-
// Silent fail - analytics shouldn't break MCP requests
259+
}).catch((error) => {
260+
console.warn('Analytics collection failed:', error, 'Data:', analyticsData);
260261
});
261262

262263
} catch (error) {

app/oauth/authorize/page.tsx

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -162,42 +162,43 @@ export default async function AuthorizePage({
162162
}
163163

164164
return (
165-
<main className="flex items-center justify-center h-screen bg-gray-50">
166-
<div className="bg-white p-8 rounded-lg shadow-md max-w-sm w-full">
167-
<h1 className="text-xl font-semibold mb-4 text-center">
168-
Authorize Application
169-
</h1>
170-
<div className="text-center">
171-
<p className="mb-2">
172-
The application{' '}
173-
<strong className="font-medium">{client.name}</strong> is
174-
requesting access to your account.
175-
</p>
176-
<p className="text-sm text-gray-600">
177-
Do you want to grant access?
178-
</p>
179-
</div>
180-
<form action={handleConsent} className="mt-6">
181-
<div className="flex justify-center gap-4">
165+
<div className="min-h-screen bg-gradient-to-br from-base-50 via-primary-100 to-secondary-300 dark:from-base-950 dark:via-base-800 dark:to-base-800 flex items-center justify-center">
166+
<div className="w-full max-w-md">
167+
<div className="bg-background border border-border rounded-xl shadow-lg p-8 space-y-6">
168+
<div className="text-center">
169+
<h1 className="text-2xl font-bold text-foreground mb-2">
170+
Authorize Application
171+
</h1>
172+
<p className="text-muted-foreground mb-4">
173+
The application{' '}
174+
<strong className="font-medium text-foreground">{client.name}</strong> is
175+
requesting access to your account.
176+
</p>
177+
<p className="text-sm text-muted-foreground">
178+
Do you want to grant access?
179+
</p>
180+
</div>
181+
182+
<form action={handleConsent} className="space-y-4">
182183
<button
183184
type="submit"
184185
name="consent"
185186
value="allow"
186-
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"
187+
className="w-full bg-primary hover:bg-primary-800 text-primary-foreground px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50 transition-colors"
187188
>
188189
Allow
189190
</button>
190191
<button
191192
type="submit"
192193
name="consent"
193194
value="deny"
194-
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-opacity-50"
195+
className="w-full border border-border text-foreground hover:bg-muted px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-border focus:ring-opacity-50 transition-colors"
195196
>
196197
Deny
197198
</button>
198-
</div>
199-
</form>
199+
</form>
200+
</div>
200201
</div>
201-
</main>
202+
</div>
202203
);
203204
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"use client"
2+
3+
import { Pie, PieChart, Cell, ResponsiveContainer } from "recharts"
4+
import {
5+
Card,
6+
CardContent,
7+
CardDescription,
8+
CardHeader,
9+
CardTitle,
10+
} from "@/components/ui/card"
11+
import {
12+
ChartConfig,
13+
ChartContainer,
14+
ChartLegend,
15+
ChartLegendContent,
16+
} from "@/components/ui/chart"
17+
18+
interface PieChartData {
19+
name: string
20+
value: number
21+
percentage?: number
22+
fill?: string
23+
}
24+
25+
interface AnalyticsPieChartProps {
26+
title: string
27+
description?: string
28+
data: PieChartData[]
29+
dataKey?: string
30+
nameKey?: string
31+
className?: string
32+
config?: ChartConfig
33+
}
34+
35+
// Default color palette using your globals.css colors
36+
const defaultColors = [
37+
"var(--primary-600)", // Main primary color
38+
"var(--secondary-600)", // Secondary color
39+
"var(--primary-300)", // Lighter primary
40+
"var(--secondary-300)", // Lighter secondary
41+
"var(--primary-800)", // Darker primary
42+
"var(--secondary-800)", // Darker secondary
43+
"var(--base-600)", // Neutral
44+
"var(--base-400)" // Light neutral
45+
]
46+
47+
export function AnalyticsPieChart({
48+
title,
49+
description,
50+
data,
51+
dataKey = "value",
52+
nameKey = "name",
53+
className,
54+
config
55+
}: AnalyticsPieChartProps) {
56+
57+
// Add colors to data if not provided
58+
const chartData = data.map((item, index) => ({
59+
...item,
60+
fill: item.fill || defaultColors[index % defaultColors.length]
61+
}))
62+
63+
// Generate config if not provided
64+
const chartConfig = config || chartData.reduce((acc, item, index) => {
65+
const key = item.name.toLowerCase().replace(/[^a-z0-9]/g, '')
66+
acc[key] = {
67+
label: item.name,
68+
color: item.fill || defaultColors[index % defaultColors.length]
69+
}
70+
return acc
71+
}, {} as ChartConfig)
72+
73+
return (
74+
<Card className={`flex flex-col ${className}`}>
75+
<CardHeader className="items-center pb-0">
76+
<CardTitle>{title}</CardTitle>
77+
{description && (
78+
<CardDescription>{description}</CardDescription>
79+
)}
80+
</CardHeader>
81+
<CardContent className="flex-1 pb-0">
82+
<ChartContainer
83+
config={chartConfig}
84+
className="mx-auto aspect-square max-h-[300px]"
85+
>
86+
<PieChart>
87+
<Pie
88+
data={chartData}
89+
dataKey={dataKey}
90+
nameKey={nameKey}
91+
cx="50%"
92+
cy="50%"
93+
outerRadius={80}
94+
>
95+
{chartData.map((entry, index) => (
96+
<Cell key={`cell-${index}`} fill={entry.fill} />
97+
))}
98+
</Pie>
99+
<ChartLegend
100+
content={<ChartLegendContent nameKey={nameKey} />}
101+
className="-translate-y-2 flex-wrap gap-2 *:basis-1/4 *:justify-center"
102+
/>
103+
</PieChart>
104+
</ChartContainer>
105+
</CardContent>
106+
</Card>
107+
)
108+
}

0 commit comments

Comments
 (0)