import React, { useState, useEffect, useRef } from 'react'; import axios from 'axios'; import { formatLocalTime } from './utils/timeUtils'; import './Observability.css'; function Observability() { const [activeTab, setActiveTab] = useState('metrics'); const [metrics, setMetrics] = useState(null); const [errors, setErrors] = useState([]); const [auditLogs, setAuditLogs] = useState([]); const [auditSummary, setAuditSummary] = useState(null); const [expandedAuditRow, setExpandedAuditRow] = useState(null); const [filters, setFilters] = useState({ entityType: '', entityId: '', userId: '', action: '' }); const wsRef = useRef(null); const wsReconnectTimer = useRef(null); const wsReconnectDelay = useRef(1000); useEffect(() => { loadData(); const interval = setInterval(loadData, 30000); return () => clearInterval(interval); // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeTab, filters]); useEffect(() => { const connect = () => { try { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const ws = new WebSocket(`${protocol}//${window.location.host}`); ws.onmessage = (event) => { try { const msg = JSON.parse(event.data); if (msg.type === 'metrics' && activeTab === 'metrics') { setMetrics(msg.data); } } catch (_) {} }; ws.onopen = () => { wsReconnectDelay.current = 1000; }; ws.onclose = () => { wsRef.current = null; wsReconnectTimer.current = setTimeout(() => { wsReconnectDelay.current = Math.min(wsReconnectDelay.current * 2, 30000); connect(); }, wsReconnectDelay.current); }; ws.onerror = () => {}; wsRef.current = ws; } catch (_) {} }; connect(); return () => { clearTimeout(wsReconnectTimer.current); if (wsRef.current) wsRef.current.close(); }; }, [activeTab]); const loadData = async () => { try { if (activeTab === 'metrics') { const resp = await axios.get('/api/observability/metrics'); setMetrics(resp.data); } else if (activeTab === 'errors') { const resp = await axios.get('/api/observability/errors?limit=50'); setErrors(resp.data.errors); } else if (activeTab === 'audit') { const auditResp = await axios.get('/api/observability/audit-logs', { params: filters }); setAuditLogs(auditResp.data.logs); setAuditSummary(auditResp.data.summary); } } catch (error) { // Error loading data } }; return (
| Endpoint | Requests | Success Rate | Avg Response | Min/Max |
|---|---|---|---|---|
{stat.method} {stat.endpoint}
|
{stat.totalRequests} | = 95 ? 'is-good' : 'is-warn'}`}> {stat.successRate}% | {stat.avgResponseTime}ms | {stat.minResponseTime}ms / {stat.maxResponseTime}ms |
No errors logged
) : (| Error Message | Context | Occurred At |
|---|---|---|
{error.error_message}
|
{error.context ? JSON.stringify(JSON.parse(error.context)).substring(0, 50) : '-'} | {formatLocalTime(error.occurred_at)} |
Summary (Last 7 Days): {auditSummary.totalEvents} events
{auditSummary.actionBreakdown.map(action => ({action.action}: {action.count}
))}No audit logs found
) : (| Action | Entity | User | Changes | Time |
|---|---|---|---|---|
| {log.action} | {log.entity_type} #{log.entity_id} | {log.user_id} |
{expandedAuditRow === i ? '▼ Hide' : '▶ Show'} Changes
|
{formatLocalTime(log.created_at)} |
Full Changes:
{JSON.stringify(log.changes, null, 2)}
|
||||