File size: 5,432 Bytes
9e4583c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
"use client";

/**
 * SystemMonitor — Real-time system metrics widget
 *
 * Displays CPU/memory/uptime and token throughput metrics.
 * Fetches from /api/monitoring/health periodically.
 *
 * @module shared/components/SystemMonitor
 */

import { useState, useEffect, useRef } from "react";
import Card from "./Card";

const REFRESH_INTERVAL = 10000; // 10s

function formatUptime(seconds) {
  if (!seconds) return "N/A";
  const d = Math.floor(seconds / 86400);
  const h = Math.floor((seconds % 86400) / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  if (d > 0) return `${d}d ${h}h`;
  if (h > 0) return `${h}h ${m}m`;
  return `${m}m`;
}

function formatBytes(bytes) {
  if (!bytes) return "0 B";
  const units = ["B", "KB", "MB", "GB"];
  let i = 0;
  let val = bytes;
  while (val >= 1024 && i < units.length - 1) {
    val /= 1024;
    i++;
  }
  return `${val.toFixed(1)} ${units[i]}`;
}

function MetricRow({ icon, label, value, color = "text-text-main" }) {
  return (
    <div className="flex items-center justify-between py-1.5">
      <div className="flex items-center gap-2 text-text-muted">
        <span className="material-symbols-outlined text-[16px]" aria-hidden="true">
          {icon}
        </span>
        <span className="text-sm">{label}</span>
      </div>
      <span className={`text-sm font-mono font-medium ${color}`}>{value}</span>
    </div>
  );
}

export default function SystemMonitor({ compact = false }) {
  const [metrics, setMetrics] = useState(null);
  const [error, setError] = useState(false);
  const mountedRef = useRef(true);

  useEffect(() => {
    mountedRef.current = true;

    async function poll() {
      try {
        const res = await fetch("/api/monitoring/health");
        if (!mountedRef.current) return;
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const data = await res.json();
        if (!mountedRef.current) return;
        setMetrics(data);
        setError(false);
      } catch {
        if (mountedRef.current) setError(true);
      }
    }

    poll();
    const interval = setInterval(poll, REFRESH_INTERVAL);
    return () => {
      mountedRef.current = false;
      clearInterval(interval);
    };
  }, []);

  if (error && !metrics) {
    return (
      <Card className="p-4">
        <div className="flex items-center gap-2 text-text-muted text-sm">
          <span className="material-symbols-outlined text-[18px] text-red-400">error</span>
          <span>Unable to load system metrics</span>
        </div>
      </Card>
    );
  }

  if (!metrics) {
    return (
      <Card className="p-4 animate-pulse">
        <div className="h-24 bg-surface rounded-lg" />
      </Card>
    );
  }

  const memUsed = metrics.memory?.heapUsed || metrics.memoryUsage?.heapUsed || 0;
  const memTotal = metrics.memory?.heapTotal || metrics.memoryUsage?.heapTotal || 1;
  const memPercent = Math.round((memUsed / memTotal) * 100);
  const memColor =
    memPercent > 80 ? "text-red-400" : memPercent > 60 ? "text-amber-400" : "text-green-400";

  if (compact) {
    return (
      <div
        className="flex items-center gap-4 text-xs text-text-muted"
        role="status"
        aria-label="System metrics"
      >
        <span title={`Memory: ${memPercent}%`}>
          <span className={`font-mono font-medium ${memColor}`}>{memPercent}%</span> mem
        </span>
        <span title={`Uptime: ${formatUptime(metrics.uptime)}`}>
          ⏱ {formatUptime(metrics.uptime)}
        </span>
      </div>
    );
  }

  return (
    <Card className="p-4" role="region" aria-label="System monitoring">
      <h3 className="text-sm font-semibold text-text-main mb-3 flex items-center gap-2">
        <span className="material-symbols-outlined text-[18px] text-primary" aria-hidden="true">
          monitoring
        </span>
        System Monitor
      </h3>

      <div className="space-y-0.5">
        <MetricRow icon="timer" label="Uptime" value={formatUptime(metrics.uptime)} />
        <MetricRow
          icon="memory"
          label="Memory"
          value={`${formatBytes(memUsed)} / ${formatBytes(memTotal)} (${memPercent}%)`}
          color={memColor}
        />
        {metrics.version && (
          <MetricRow icon="info" label="Version" value={metrics.version} color="text-primary" />
        )}
        {metrics.activeConnections !== undefined && (
          <MetricRow icon="lan" label="Connections" value={String(metrics.activeConnections)} />
        )}
        {metrics.circuitBreakers && (
          <MetricRow
            icon="health_and_safety"
            label="Circuit Breakers"
            value={`${metrics.circuitBreakers.open || 0} open`}
            color={metrics.circuitBreakers.open > 0 ? "text-amber-400" : "text-green-400"}
          />
        )}
      </div>

      {/* Memory bar */}
      <div className="mt-3">
        <div className="h-1.5 bg-surface rounded-full overflow-hidden">
          <div
            className={`h-full rounded-full transition-all duration-500 ${
              memPercent > 80 ? "bg-red-400" : memPercent > 60 ? "bg-amber-400" : "bg-green-400"
            }`}
            style={{ width: `${memPercent}%` }}
            role="progressbar"
            aria-valuenow={memPercent}
            aria-valuemin={0}
            aria-valuemax={100}
            aria-label={`Memory usage: ${memPercent}%`}
          />
        </div>
      </div>
    </Card>
  );
}