diff --git a/frontend/src/pages/TargetDetailPage.jsx b/frontend/src/pages/TargetDetailPage.jsx
index d33df01..f6f7983 100644
--- a/frontend/src/pages/TargetDetailPage.jsx
+++ b/frontend/src/pages/TargetDetailPage.jsx
@@ -36,6 +36,24 @@ function formatSeconds(value) {
return `${(value / 3600).toFixed(1)}h`;
}
+function formatNumber(value, digits = 2) {
+ if (value === null || value === undefined || Number.isNaN(Number(value))) return "-";
+ return Number(value).toFixed(digits);
+}
+
+function MetricsTooltip({ active, payload, label }) {
+ if (!active || !payload || payload.length === 0) return null;
+ const row = payload[0]?.payload || {};
+ return (
+
+
{label}
+
connections: {formatNumber(row.connections, 0)}
+
tps: {formatNumber(row.tps, 2)}
+
cache: {formatNumber(row.cache, 2)}%
+
+ );
+}
+
async function loadMetric(targetId, metric, range, tokens, refresh) {
const { from, to } = toQueryRange(range);
return apiFetch(
@@ -91,13 +109,29 @@ export function TargetDetailPage() {
}, [id, range, tokens, refresh]);
const chartData = useMemo(
- () =>
- (series.connections || []).map((point, idx) => ({
- ts: new Date(point.ts).toLocaleTimeString(),
- connections: point.value,
- xacts: series.xacts?.[idx]?.value || 0,
- cache: series.cache?.[idx]?.value || 0,
- })),
+ () => {
+ const con = series.connections || [];
+ const xacts = series.xacts || [];
+ const cache = series.cache || [];
+ return con.map((point, idx) => {
+ const prev = xacts[idx - 1];
+ const curr = xacts[idx];
+ let tps = 0;
+ if (prev && curr) {
+ const dt = (new Date(curr.ts).getTime() - new Date(prev.ts).getTime()) / 1000;
+ const dx = (curr.value || 0) - (prev.value || 0);
+ if (dt > 0 && dx >= 0) {
+ tps = dx / dt;
+ }
+ }
+ return {
+ ts: new Date(point.ts).toLocaleTimeString(),
+ connections: point.value,
+ tps,
+ cache: (cache[idx]?.value || 0) * 100,
+ };
+ });
+ },
[series]
);
@@ -232,11 +266,12 @@ export function TargetDetailPage() {
-
-
-
-
-
+
+
+ } />
+
+
+
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 98ddcb0..6f2c718 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -359,6 +359,37 @@ td {
margin: 8px 0 0 18px;
}
+.chart-tooltip {
+ background: #0f1934ee;
+ border: 1px solid #2f4a8b;
+ border-radius: 10px;
+ padding: 10px 12px;
+ min-width: 170px;
+}
+
+.chart-tooltip-time {
+ font-size: 12px;
+ color: #9cb2d8;
+ margin-bottom: 6px;
+}
+
+.chart-tooltip-item {
+ font-size: 14px;
+ margin: 3px 0;
+}
+
+.chart-tooltip-item.c1 {
+ color: #38bdf8;
+}
+
+.chart-tooltip-item.c2 {
+ color: #22c55e;
+}
+
+.chart-tooltip-item.c3 {
+ color: #f59e0b;
+}
+
@media (max-width: 980px) {
body {
overflow: auto;