diff --git a/app/api/metrics/route.ts b/app/api/metrics/route.ts index d4b5be4..f3cf6d1 100644 --- a/app/api/metrics/route.ts +++ b/app/api/metrics/route.ts @@ -6,6 +6,16 @@ import type { SystemMetrics } from "@/types/metrics"; export const dynamic = "force-dynamic"; export const revalidate = 0; +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", +}; + +export async function OPTIONS(): Promise { + return NextResponse.json({}, { headers: corsHeaders }); +} + async function getCpuMetrics() { const [cpu, cpuLoad, cpuSpeed, cpuTemp, cpuCache] = await Promise.all([ si.cpu(), @@ -317,9 +327,9 @@ export async function GET(): Promise { rocmRuntimeVersion: gpuData.rocmRuntimeVersion, }; - return NextResponse.json(response); + return NextResponse.json(response, { headers: corsHeaders }); } catch (error) { console.error("Failed to collect metrics:", error); - return NextResponse.json({ error: "Failed to collect metrics" }, { status: 500 }); + return NextResponse.json({ error: "Failed to collect metrics" }, { status: 500, headers: corsHeaders }); } } diff --git a/lib/components/Dashboard.tsx b/lib/components/Dashboard.tsx index f8dd0ef..22da968 100644 --- a/lib/components/Dashboard.tsx +++ b/lib/components/Dashboard.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState, useCallback } from "react"; +import { useEffect, useState, useCallback, useRef } from "react"; import { ThemeProvider, useTheme } from "./ThemeContext"; import type { SystemMetrics } from "@/types/metrics"; import { @@ -590,9 +590,9 @@ function GpuColumn({ AMD ROCm Powered by ROCm {rocmRuntimeVersion} @@ -863,6 +863,7 @@ export function DashboardContent() { const [activeTab, setActiveTab] = useState("cpu"); const [showPerCore, setShowPerCore] = useState(true); const [showGpuHardware, setShowGpuHardware] = useState(true); + const fetchControllerRef = useRef(null); const toggleTab = (id: TabId) => { setVisibleTabs((prev) => { @@ -880,24 +881,48 @@ export function DashboardContent() { }); }; - const fetchMetrics = useCallback(async () => { - try { - const res = await fetch("/api/metrics"); - if (!res.ok) throw new Error("Failed to fetch"); - const data: SystemMetrics = await res.json(); - setMetrics(data); - setLastUpdate(new Date(data.timestamp)); - setIsLoading(false); - } catch (error) { - console.error("Failed to fetch metrics:", error); - setIsLoading(false); + const fetchMetrics = useCallback(() => { + // Abort any pending request (handles React Strict Mode double-invoke) + if (fetchControllerRef.current) { + fetchControllerRef.current.abort(); } + const controller = new AbortController(); + fetchControllerRef.current = controller; + const timeout = setTimeout(() => controller.abort(), 5000); + const url = `${window.location.origin}/api/metrics`; + + fetch(url, { + signal: controller.signal, + credentials: "same-origin", + cache: "no-store", + mode: "same-origin", + }) + .then((res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`); + return res.json(); + }) + .then((data: SystemMetrics) => { + setMetrics(data); + setLastUpdate(new Date(data.timestamp)); + setIsLoading(false); + }) + .catch((error) => { + if (error.name === "AbortError") return; + console.error("Failed to fetch metrics:", error.name, error.message); + setIsLoading(false); + }) + .finally(() => clearTimeout(timeout)); }, []); useEffect(() => { fetchMetrics(); const interval = setInterval(fetchMetrics, UPDATE_INTERVAL); - return () => clearInterval(interval); + return () => { + clearInterval(interval); + if (fetchControllerRef.current) { + fetchControllerRef.current.abort(); + } + }; }, [fetchMetrics]); const getVisibleColumns = () => { @@ -917,7 +942,7 @@ export function DashboardContent() { >
- Logo + Logo

System Metrics Plus