EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
TabMetrics.jsx
Go to the documentation of this file.
1import React from "react";
2import { Line } from "react-chartjs-2";
3import { apiFetch } from "../../lib/api";
4import {
5 Chart as ChartJS,
6 CategoryScale,
7 LinearScale,
8 PointElement,
9 LineElement,
10 Title,
11 Tooltip,
12 Legend,
13 Filler,
14} from "chart.js";
15
16ChartJS.register(
17 CategoryScale,
18 LinearScale,
19 PointElement,
20 LineElement,
21 Title,
22 Tooltip,
23 Legend,
24 Filler
25);
26
27export default function TabMetrics({ agentId, wsData }) {
28 const [history, setHistory] = React.useState([]);
29 const [loading, setLoading] = React.useState(true);
30 const [error, setError] = React.useState(null);
31 const [realtimeMetric, setRealtimeMetric] = React.useState(null);
32
33 // Load historical data from API
34 React.useEffect(() => {
35 if (!agentId) return;
36 setLoading(true);
37 apiFetch(`/agent/${agentId}/metrics?limit=100`)
38 .then((res) => res.json())
39 .then((d) => {
40 console.log('[TabMetrics] Received data:', d);
41 if (Array.isArray(d)) {
42 console.log('[TabMetrics] Data is array, length:', d.length);
43 // Reverse to show oldest first (chronological order)
44 setHistory(d.reverse());
45 } else {
46 console.log('[TabMetrics] Data is not array:', typeof d);
47 setHistory([]);
48 }
49 })
50 .catch((err) => {
51 console.error("Failed to load metrics:", err);
52 setError(err.message);
53 })
54 .finally(() => setLoading(false));
55 }, [agentId]);
56
57 // Update realtime metric from WebSocket
58 React.useEffect(() => {
59 if (wsData) {
60 console.log('[TabMetrics] WebSocket data received:', wsData);
61 setRealtimeMetric({
62 timestamp: new Date().toISOString(),
63 cpu: wsData.cpuLoad1 || wsData.cpu || 0,
64 memory: wsData.memPercent || wsData.memory || 0,
65 disk: Array.isArray(wsData.disk) ? wsData.disk.reduce((sum, d) => sum + (d.used || 0), 0) : (wsData.disk || 0),
66 uptime: wsData.uptime || 0,
67 raw: wsData
68 });
69 }
70 }, [wsData]);
71
72 if (loading) {
73 return (
74 <div className="tab-container">
75 <h2>Performance Metrics</h2>
76 <div className="card">Loading metrics...</div>
77 </div>
78 );
79 }
80
81 if (error) {
82 return (
83 <div className="tab-container">
84 <h2>Performance Metrics</h2>
85 <div className="card error">Error: {error}</div>
86 </div>
87 );
88 }
89
90 // Combine historical data with realtime metric for display
91 let displayData = history;
92 if (realtimeMetric && history.length > 0) {
93 // Append realtime data to history for charts
94 displayData = [...history, realtimeMetric];
95 } else if (realtimeMetric && history.length === 0) {
96 // Only have realtime data, use it alone
97 displayData = [realtimeMetric];
98 }
99
100 if (!Array.isArray(displayData) || displayData.length === 0) {
101 return (
102 <div className="tab-container">
103 <h2>Performance Metrics</h2>
104 <div className="card">
105 <p style={{ textAlign: "center", color: "var(--text-secondary)" }}>
106 No metrics data available yet. Metrics are collected in real-time from the agent.
107 </p>
108 </div>
109 </div>
110 );
111 }
112
113 // Take last 50 data points for better readability
114 const recent = displayData.slice(-50);
115
116 const labels = recent.map((h) => {
117 const date = new Date(h.timestamp);
118 return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
119 });
120
121 const cpuData = recent.map((h) => parseFloat(h.cpu) || 0);
122 const memoryData = recent.map((h) => parseFloat(h.memory) || 0);
123 const diskData = recent.map((h) => {
124 // disk is in bytes, convert to GB
125 const diskBytes = parseFloat(h.disk) || 0;
126 return (diskBytes / (1024 * 1024 * 1024)).toFixed(2);
127 });
128
129 const cpuChartData = {
130 labels,
131 datasets: [
132 {
133 label: "CPU Usage (%)",
134 data: cpuData,
135 borderColor: "rgb(75, 192, 192)",
136 backgroundColor: "rgba(75, 192, 192, 0.1)",
137 tension: 0.4,
138 fill: true,
139 },
140 ],
141 };
142
143 const memoryChartData = {
144 labels,
145 datasets: [
146 {
147 label: "Memory Usage (%)",
148 data: memoryData,
149 borderColor: "rgb(255, 99, 132)",
150 backgroundColor: "rgba(255, 99, 132, 0.1)",
151 tension: 0.4,
152 fill: true,
153 },
154 ],
155 };
156
157 const diskChartData = {
158 labels,
159 datasets: [
160 {
161 label: "Disk Usage (GB)",
162 data: diskData,
163 borderColor: "rgb(153, 102, 255)",
164 backgroundColor: "rgba(153, 102, 255, 0.1)",
165 tension: 0.4,
166 fill: true,
167 },
168 ],
169 };
170
171 const chartOptions = {
172 responsive: true,
173 maintainAspectRatio: false,
174 plugins: {
175 legend: {
176 display: true,
177 position: "top",
178 },
179 tooltip: {
180 mode: "index",
181 intersect: false,
182 },
183 },
184 scales: {
185 y: {
186 beginAtZero: true,
187 grid: {
188 color: "rgba(0, 0, 0, 0.05)",
189 },
190 },
191 x: {
192 grid: {
193 display: false,
194 },
195 },
196 },
197 };
198
199 const diskChartOptions = {
200 ...chartOptions,
201 scales: {
202 ...chartOptions.scales,
203 y: {
204 ...chartOptions.scales.y,
205 ticks: {
206 callback: function (value) {
207 return value + " GB";
208 },
209 },
210 },
211 },
212 };
213
214 // Calculate current stats - prefer realtime data if available
215 const latest = realtimeMetric || recent[recent.length - 1];
216 const currentCPU = parseFloat(latest?.cpu || 0).toFixed(1);
217 const currentMemory = parseFloat(latest?.memory || 0).toFixed(1);
218 const currentDisk = (parseFloat(latest?.disk || 0) / (1024 * 1024 * 1024)).toFixed(2);
219 const uptime = parseInt(latest?.uptime || 0);
220 const uptimeHours = Math.floor(uptime / 3600);
221 const uptimeDays = Math.floor(uptimeHours / 24);
222
223 return (
224 <div className="tab-container">
225 <h2>Performance Metrics</h2>
226
227 {/* Current Stats Summary */}
228 <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: "16px", marginBottom: "24px" }}>
229 <div className="card" style={{ padding: "16px" }}>
230 <div style={{ fontSize: "12px", color: "var(--text-secondary)", marginBottom: "4px" }}>CPU Usage</div>
231 <div style={{ fontSize: "24px", fontWeight: "600", color: "var(--text)" }}>{currentCPU}%</div>
232 </div>
233 <div className="card" style={{ padding: "16px" }}>
234 <div style={{ fontSize: "12px", color: "var(--text-secondary)", marginBottom: "4px" }}>Memory Usage</div>
235 <div style={{ fontSize: "24px", fontWeight: "600", color: "var(--text)" }}>{currentMemory}%</div>
236 </div>
237 <div className="card" style={{ padding: "16px" }}>
238 <div style={{ fontSize: "12px", color: "var(--text-secondary)", marginBottom: "4px" }}>Disk Used</div>
239 <div style={{ fontSize: "24px", fontWeight: "600", color: "var(--text)" }}>{currentDisk} GB</div>
240 </div>
241 <div className="card" style={{ padding: "16px" }}>
242 <div style={{ fontSize: "12px", color: "var(--text-secondary)", marginBottom: "4px" }}>Uptime</div>
243 <div style={{ fontSize: "24px", fontWeight: "600", color: "var(--text)" }}>
244 {uptimeDays > 0 ? `${uptimeDays}d ${uptimeHours % 24}h` : `${uptimeHours}h`}
245 </div>
246 </div>
247 </div>
248
249 {/* CPU Chart */}
250 <div className="card" style={{ padding: "20px", marginBottom: "20px" }}>
251 <h3 style={{ marginTop: 0, marginBottom: "16px", fontSize: "16px", fontWeight: "600" }}>
252 CPU Usage Over Time
253 </h3>
254 <div style={{ height: "250px" }}>
255 <Line data={cpuChartData} options={chartOptions} />
256 </div>
257 </div>
258
259 {/* Memory Chart */}
260 <div className="card" style={{ padding: "20px", marginBottom: "20px" }}>
261 <h3 style={{ marginTop: 0, marginBottom: "16px", fontSize: "16px", fontWeight: "600" }}>
262 Memory Usage Over Time
263 </h3>
264 <div style={{ height: "250px" }}>
265 <Line data={memoryChartData} options={chartOptions} />
266 </div>
267 </div>
268
269 {/* Disk Chart */}
270 <div className="card" style={{ padding: "20px" }}>
271 <h3 style={{ marginTop: 0, marginBottom: "16px", fontSize: "16px", fontWeight: "600" }}>
272 Disk Usage Over Time
273 </h3>
274 <div style={{ height: "250px" }}>
275 <Line data={diskChartData} options={diskChartOptions} />
276 </div>
277 </div>
278
279 <div style={{ marginTop: "16px", fontSize: "12px", color: "var(--text-secondary)", textAlign: "center" }}>
280 Showing last {recent.length} data points • Metrics collected every 2 minutes
281 </div>
282 </div>
283 );
284}