"use client" import { useState, useEffect } from "react" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" import { Thermometer, TrendingDown, TrendingUp, Minus } from "lucide-react" import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts" import { useIsMobile } from "../hooks/use-mobile" import { fetchApi } from "@/lib/api-config" import { useDiskTempThresholds, type DiskTempThreshold } from "@/lib/health-thresholds" const TIMEFRAME_OPTIONS = [ { value: "hour", label: "1 Hour" }, { value: "day", label: "24 Hours" }, { value: "week", label: "7 Days" }, { value: "month", label: "30 Days" }, ] interface TempHistoryPoint { timestamp: number value: number min?: number max?: number } interface TempStats { min: number max: number avg: number current: number } interface DiskTemperatureDetailModalProps { open: boolean onOpenChange: (open: boolean) => void diskName: string diskModel?: string liveTemperature?: number diskType?: "HDD" | "SSD" | "NVMe" | "SAS" | string } const CustomTooltip = ({ active, payload, label }: any) => { if (active && payload && payload.length) { return (

{label}

{payload.map((entry: any, index: number) => (
{entry.name}: {entry.value}°C
))}
) } return null } // Per-disk-class thresholds come from the user-configurable backend // (lib/health-thresholds.ts), so the chart line color stays in sync // with whatever the user sets in Settings → Health Monitor Thresholds. function colorFor(temp: number, t: DiskTempThreshold): string { if (temp >= t.hot) return "#ef4444" if (temp >= t.warn) return "#f59e0b" return "#22c55e" } function statusInfoFor(temp: number, t: DiskTempThreshold) { if (temp <= 0) return { status: "N/A", color: "bg-gray-500/10 text-gray-500 border-gray-500/20" } if (temp >= t.hot) return { status: "Hot", color: "bg-red-500/10 text-red-500 border-red-500/20" } if (temp >= t.warn) return { status: "Warm", color: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" } return { status: "Normal", color: "bg-green-500/10 text-green-500 border-green-500/20" } } export function DiskTemperatureDetailModal({ open, onOpenChange, diskName, diskModel, liveTemperature, diskType, }: DiskTemperatureDetailModalProps) { const [timeframe, setTimeframe] = useState("day") const [data, setData] = useState([]) const [stats, setStats] = useState({ min: 0, max: 0, avg: 0, current: 0 }) const [loading, setLoading] = useState(true) const isMobile = useIsMobile() useEffect(() => { if (open && diskName) { fetchHistory() } }, [open, timeframe, diskName]) const fetchHistory = async () => { setLoading(true) try { const result = await fetchApi<{ data: TempHistoryPoint[]; stats: TempStats }>( `/api/disk/${encodeURIComponent(diskName)}/temperature/history?timeframe=${timeframe}`, ) if (result && result.data) { setData(result.data) setStats(result.stats) } else { setData([]) setStats({ min: 0, max: 0, avg: 0, current: 0 }) } } catch (err) { console.error("[ProxMenux] Failed to fetch disk temperature history:", err) setData([]) } finally { setLoading(false) } } const formatTime = (timestamp: number) => { const date = new Date(timestamp * 1000) if (timeframe === "hour" || timeframe === "day") { return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) } return date.toLocaleDateString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }) } const chartData = data.map((d) => ({ ...d, time: formatTime(d.timestamp) })) const currentTemp = liveTemperature && liveTemperature > 0 ? Math.round(liveTemperature * 10) / 10 : stats.current const allThresholds = useDiskTempThresholds() const dt: DiskTempThreshold = (() => { const t = (diskType || "").toUpperCase() if (t === "HDD") return allThresholds.HDD if (t === "NVME") return allThresholds.NVMe if (t === "SAS") return allThresholds.SAS return allThresholds.SSD })() const chartColor = colorFor(currentTemp, dt) const currentStatus = statusInfoFor(currentTemp, dt) const values = data.map((d) => d.value) const yMin = values.length > 0 ? Math.max(0, Math.floor(Math.min(...values) - 3)) : 0 const yMax = values.length > 0 ? Math.ceil(Math.max(...values) + 3) : 100 return ( {/* Header layout mirrors temperature-detail-modal exactly so the mobile breakpoints behave the same. Earlier we tried to inline the model name in the DialogTitle, but the long WD/Samsung strings broke `truncate` and pushed the dialog past the viewport — clipping the timeframe selector and the right two stat cards. Keeping the title short and parking the model in a second line (DialogDescription) lets the standard mobile grid render correctly. */}
/dev/{diskName}
{diskModel && (

{diskModel}

)}
Current
{currentTemp > 0 ? `${currentTemp}°C` : "N/A"}
Min
{stats.min}°C
Avg
{stats.avg}°C
Max
{stats.max}°C
{loading ? (
) : chartData.length === 0 ? (

No temperature data yet for this disk

Samples are collected every 60 seconds

) : ( `${v}°`} width={isMobile ? 40 : 45} /> } /> )}
) }