Skip to content

Commit 9eb2bd2

Browse files
committed
v0.2
1 parent 605f68b commit 9eb2bd2

File tree

5 files changed

+542
-47
lines changed

5 files changed

+542
-47
lines changed

app/analytics/page.tsx

Lines changed: 247 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ export default function AnalyticsPage() {
1515
security?: {
1616
eventCount: number;
1717
events?: { timestamp: string; eventType: string; ipAddress: string; clientId?: string; details: string }[];
18+
byOrganization?: { organization: string; eventType: string; severity: string; eventCount: number; avgRiskScore: number }[];
19+
privilegeEscalations?: { userName: string; userEmail: string; eventType: string; riskScore: number; timestamp: string; details: Record<string, unknown> }[];
20+
};
21+
enterprise?: {
22+
usersByMCPServer?: { userName: string; userEmail: string; mcpServerName: string; mcpServerIdentifier: string }[];
23+
toolUsage?: { toolName: string; mcpMethod: string; usageCount: number; uniqueUsers: number }[];
1824
};
1925
lastUpdated?: string;
2026
timeRange?: string;
@@ -163,55 +169,132 @@ export default function AnalyticsPage() {
163169
</div>
164170
)}
165171

166-
{/* Security Events */}
167-
<div className="analytics-section">
168-
<h2>🔒 Security Events</h2>
169-
<div className="security-overview">
170-
<div className="metric-card security-card">
171-
<h4>Total Events</h4>
172-
<p className="metric-value">{data.security?.eventCount || 0}</p>
173-
</div>
174-
{(!data.security?.eventCount || data.security.eventCount === 0) && (
175-
<p className="no-events">No security events detected in the selected time range</p>
172+
173+
{/* Enterprise Analytics */}
174+
{data.enterprise && (
175+
<div className="analytics-section">
176+
<h2>Enterprise Analytics</h2>
177+
178+
{/* Which MCP servers are users using? */}
179+
{data.enterprise.usersByMCPServer && data.enterprise.usersByMCPServer.length > 0 && (
180+
<div className="enterprise-subsection">
181+
<h3>Which MCP servers are users using?</h3>
182+
<div className="user-mcp-list">
183+
{data.enterprise.usersByMCPServer.map((item, index) => (
184+
<div key={index} className="user-mcp-item">
185+
<div className="user-info">
186+
<strong>{item.userName}</strong>
187+
<span className="user-email">{item.userEmail}</span>
188+
</div>
189+
<div className="mcp-info">
190+
<span className="mcp-name">{item.mcpServerName}</span>
191+
<span className="mcp-identifier">{item.mcpServerIdentifier}</span>
192+
</div>
193+
</div>
194+
))}
195+
</div>
196+
</div>
197+
)}
198+
199+
{/* MCP Tool Usage */}
200+
{data.enterprise.toolUsage && data.enterprise.toolUsage.length > 0 && (
201+
<div className="enterprise-subsection">
202+
<h3>Most Used MCP Tools</h3>
203+
<div className="tool-usage-list">
204+
{data.enterprise.toolUsage.map((tool, index) => (
205+
<div key={index} className="tool-usage-item">
206+
<span className="tool-rank">#{index + 1}</span>
207+
<div className="tool-details">
208+
<strong>{tool.toolName}</strong>
209+
<span className="tool-method">{tool.mcpMethod}</span>
210+
</div>
211+
<div className="tool-stats">
212+
<span>{tool.usageCount} calls</span>
213+
<span>{tool.uniqueUsers} users</span>
214+
</div>
215+
</div>
216+
))}
217+
</div>
218+
</div>
176219
)}
177220
</div>
178-
{data.security?.events && data.security.events.length > 0 && (
179-
<div className="security-events">
180-
<h4>Recent Security Events</h4>
181-
{data.security.events.slice(0, 10).map((event, index) => (
182-
<div key={index} className="security-event">
183-
<div className="event-header">
184-
<span className="event-type">{event.eventType}</span>
185-
<span className="event-time">{new Date(event.timestamp).toLocaleString()}</span>
186-
</div>
187-
<div className="event-details">
188-
<span>IP: {event.ipAddress}</span>
189-
{event.clientId && <span>Client: {event.clientId}</span>}
190-
</div>
191-
<div className="event-description">{event.details}</div>
221+
)}
222+
223+
{/* Enhanced Security Events */}
224+
{data.security && (
225+
<div className="analytics-section">
226+
<h2>Security Monitoring</h2>
227+
228+
{/* Security events by organization */}
229+
{data.security.byOrganization && data.security.byOrganization.length > 0 && (
230+
<div className="enterprise-subsection">
231+
<h3>Security events by organization</h3>
232+
<div className="security-org-list">
233+
{data.security.byOrganization.map((item, index) => (
234+
<div key={index} className="security-org-item">
235+
<div className="org-name">{item.organization}</div>
236+
<div className="event-details">
237+
<span className={`event-type ${item.severity}`}>{item.eventType}</span>
238+
<span className="event-count">{item.eventCount} events</span>
239+
<span className="risk-score">Risk: {item.avgRiskScore}</span>
240+
</div>
241+
</div>
242+
))}
192243
</div>
193-
))}
244+
</div>
245+
)}
246+
247+
{/* Privilege escalation attempts */}
248+
{data.security.privilegeEscalations && data.security.privilegeEscalations.length > 0 && (
249+
<div className="enterprise-subsection">
250+
<h3>Users with elevated privilege attempts (Last 7 days)</h3>
251+
<div className="privilege-escalation-list">
252+
{data.security.privilegeEscalations.map((item, index) => (
253+
<div key={index} className="privilege-escalation-item">
254+
<div className="user-info">
255+
<strong>{item.userName}</strong>
256+
<span className="user-email">{item.userEmail}</span>
257+
</div>
258+
<div className="escalation-details">
259+
<span className="risk-score">Risk Score: {item.riskScore}</span>
260+
<span className="timestamp">{new Date(item.timestamp).toLocaleString()}</span>
261+
</div>
262+
</div>
263+
))}
264+
</div>
265+
</div>
266+
)}
267+
268+
{/* Overall security summary */}
269+
<div className="security-overview">
270+
<div className="metric-card security-card">
271+
<h4>Total Events</h4>
272+
<p className="metric-value">{data.security?.eventCount || 0}</p>
273+
</div>
274+
{(!data.security?.eventCount || data.security.eventCount === 0) && (
275+
<p className="no-events">No security events detected in the selected time range</p>
276+
)}
194277
</div>
195-
)}
196-
</div>
278+
</div>
279+
)}
197280

198281
{/* System Info */}
199282
<div className="analytics-section">
200-
<h2>📈 System Information</h2>
283+
<h2>System Information</h2>
201284
<div className="info-grid">
202285
<div className="info-card">
203-
<h4>Database Analytics</h4>
204-
<p>✅ Persistent PostgreSQL storage</p>
205-
<p>✅ Real-time performance tracking</p>
206-
<p>✅ Optimized with batching & indexing</p>
207-
<p>✅ Production-ready for Vercel</p>
286+
<h4>Enhanced Analytics</h4>
287+
<p>Enterprise user tracking with SSO context</p>
288+
<p>MCP server and tool usage monitoring</p>
289+
<p>Real-time security threat detection</p>
290+
<p>Role-based access analytics</p>
208291
</div>
209292
<div className="info-card">
210-
<h4>Data Collection</h4>
211-
<p>📊 Request analytics with geographic data</p>
212-
<p>🔒 Security event monitoring</p>
213-
<p>⚡ Non-blocking collection with batching</p>
214-
<p>🗄️ Automatic cleanup after 30 days</p>
293+
<h4>Security Features</h4>
294+
<p>Privilege escalation detection</p>
295+
<p>Geographic anomaly monitoring</p>
296+
<p>Rate limiting and brute force protection</p>
297+
<p>Token reuse and audience validation</p>
215298
</div>
216299
</div>
217300
</div>
@@ -467,6 +550,132 @@ export default function AnalyticsPage() {
467550
font-weight: bold;
468551
}
469552
553+
/* Enterprise Analytics Styles */
554+
.enterprise-subsection {
555+
margin-bottom: 2rem;
556+
padding: 1rem;
557+
background: #f8f9fa;
558+
border-radius: 6px;
559+
border-left: 4px solid #007bff;
560+
}
561+
562+
.enterprise-subsection h3 {
563+
color: #333;
564+
margin-bottom: 1rem;
565+
font-size: 1.1rem;
566+
}
567+
568+
.user-mcp-list, .tool-usage-list, .security-org-list, .privilege-escalation-list {
569+
display: flex;
570+
flex-direction: column;
571+
gap: 0.75rem;
572+
}
573+
574+
.user-mcp-item, .tool-usage-item, .security-org-item, .privilege-escalation-item {
575+
display: flex;
576+
justify-content: space-between;
577+
align-items: center;
578+
padding: 0.75rem;
579+
background: white;
580+
border-radius: 6px;
581+
border-left: 3px solid #28a745;
582+
}
583+
584+
.user-info {
585+
display: flex;
586+
flex-direction: column;
587+
}
588+
589+
.user-email {
590+
font-size: 0.8rem;
591+
color: #666;
592+
font-family: monospace;
593+
}
594+
595+
.mcp-info {
596+
display: flex;
597+
flex-direction: column;
598+
text-align: right;
599+
}
600+
601+
.mcp-name {
602+
font-weight: bold;
603+
color: #333;
604+
}
605+
606+
.mcp-identifier {
607+
font-size: 0.8rem;
608+
color: #666;
609+
font-family: monospace;
610+
}
611+
612+
.tool-rank {
613+
font-weight: bold;
614+
color: #007bff;
615+
margin-right: 1rem;
616+
min-width: 30px;
617+
}
618+
619+
.tool-details {
620+
flex: 1;
621+
display: flex;
622+
flex-direction: column;
623+
}
624+
625+
.tool-method {
626+
font-size: 0.8rem;
627+
color: #666;
628+
font-family: monospace;
629+
}
630+
631+
.tool-stats {
632+
display: flex;
633+
flex-direction: column;
634+
text-align: right;
635+
font-size: 0.9rem;
636+
color: #495057;
637+
}
638+
639+
.org-name {
640+
font-weight: bold;
641+
color: #333;
642+
}
643+
644+
.event-details {
645+
display: flex;
646+
gap: 1rem;
647+
align-items: center;
648+
}
649+
650+
.event-type {
651+
padding: 0.25rem 0.5rem;
652+
border-radius: 4px;
653+
font-size: 0.8rem;
654+
font-weight: bold;
655+
color: white;
656+
}
657+
658+
.event-type.low { background: #28a745; }
659+
.event-type.medium { background: #ffc107; color: #333; }
660+
.event-type.high { background: #fd7e14; }
661+
.event-type.critical { background: #dc3545; }
662+
663+
.event-count, .risk-score {
664+
font-size: 0.9rem;
665+
color: #495057;
666+
}
667+
668+
.escalation-details {
669+
display: flex;
670+
flex-direction: column;
671+
text-align: right;
672+
}
673+
674+
.timestamp {
675+
font-size: 0.8rem;
676+
color: #666;
677+
}
678+
470679
@media (max-width: 768px) {
471680
.metrics-grid {
472681
grid-template-columns: 1fr;

app/api/analytics/collect/route.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,17 @@ export async function POST(request: NextRequest) {
1111
return NextResponse.json({ error: 'Invalid analytics data' }, { status: 400 });
1212
}
1313

14-
// Convert ISO string back to Date object
14+
// Convert ISO string back to Date object and add enhanced fields
1515
const requestData = {
1616
...analyticsData,
17-
timestamp: new Date(analyticsData.timestamp)
17+
timestamp: new Date(analyticsData.timestamp),
18+
// Handle arrays properly
19+
scopes: analyticsData.scopes || [],
20+
// Ensure required fields have defaults
21+
statusCode: analyticsData.statusCode || 200,
22+
responseTime: analyticsData.responseTime || 0,
23+
ipAddress: analyticsData.ipAddress || '127.0.0.1',
24+
userAgent: analyticsData.userAgent || 'unknown'
1825
};
1926

2027
// Log the request using our optimized analytics system

app/api/analytics/route.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,24 @@ async function getEnhancedAnalytics(hours = 24) {
3333
}
3434

3535
// Efficient parallel queries for enhanced analytics
36-
const [performance, endpoints, geography, securityEvents] = await Promise.all([
36+
const [
37+
performance,
38+
endpoints,
39+
geography,
40+
securityEvents,
41+
usersByMCP,
42+
securityByOrg,
43+
privilegeEscalations,
44+
toolUsage
45+
] = await Promise.all([
3746
analyticsDB.getPerformanceMetrics(hours),
3847
analyticsDB.getTopEndpoints(hours, 10),
3948
analyticsDB.getGeographyStats(hours),
40-
analyticsDB.getSecurityEvents(hours)
49+
analyticsDB.getSecurityEvents(hours),
50+
analyticsDB.getUsersByMCPServer(hours),
51+
analyticsDB.getSecurityEventsByOrganization(hours),
52+
analyticsDB.getUserPrivilegeEscalations(168), // Last 7 days
53+
analyticsDB.getMCPToolUsage(hours)
4154
]);
4255

4356
const data = {
@@ -46,7 +59,13 @@ async function getEnhancedAnalytics(hours = 24) {
4659
geography,
4760
security: {
4861
events: securityEvents,
49-
eventCount: securityEvents.length
62+
eventCount: securityEvents.length,
63+
byOrganization: securityByOrg,
64+
privilegeEscalations
65+
},
66+
enterprise: {
67+
usersByMCPServer: usersByMCP,
68+
toolUsage
5069
},
5170
timeRange: `${hours} hours`,
5271
lastUpdated: new Date().toISOString()

0 commit comments

Comments
 (0)