feat: add fan curve control via temperature-based fan speed curves
- Backend HAL (hal/fans.py): NVML v2 fan read/set/reset with min/max queries - Server endpoints: GET/POST /api/fans, POST /api/fans/reset, POST /api/fans/speed - Background fan poller: reads GPU temp every 2s, interpolates fan speed from curve - Profile integration: fan_curve field saved/applied, auto-restore on shutdown - Frontend: FanCurveEditor (SVG chart with drag/add/delete points), FanMonitor sidebar - App.tsx: three-tab layout (Curve, Performance, Fans) - GaugeCard: optional history sparkline, Fan Mode card without sparkline - fan_mode field populated as 'curve' or 'auto' in GET /api/fans
This commit is contained in:
1 parent
7605d21e27
commit
477c4becae
11 files changed
+1288
-10
No files matched your search
+38
-4
@@ -7,12 +7,15 @@ import { CurveEditor } from './components/CurveEditor/CurveEditor';
|
||||
import { PointTable } from './components/PointTable/PointTable';
|
||||
import { PerformancePanel } from './components/Limits/PerformancePanel';
|
||||
import { PerformanceMonitor } from './components/Monitor/PerformanceMonitor';
|
||||
import { FanMonitor } from './components/Monitor/FanMonitor';
|
||||
import { FanCurveEditor } from './components/Fans/FanCurveEditor';
|
||||
import { ProfilePanel } from './components/Profiles/ProfilePanel';
|
||||
import { api } from './api/client';
|
||||
import { useCurveStore } from './store/curveStore';
|
||||
import { Toaster } from 'sonner';
|
||||
import { Loader, ChevronDown } from 'lucide-react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import type { FanState } from './types';
|
||||
|
||||
export default function App() {
|
||||
const gpuInfo = useGpu();
|
||||
@@ -20,7 +23,8 @@ export default function App() {
|
||||
const { monitor, monitorHistory, wsStatus: monitorWsStatus } = useMonitor();
|
||||
const { setCurve, activeProfile, setActiveProfile, selectedGpuIndex } = useCurveStore();
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'curve' | 'performance'>('curve');
|
||||
const [activeTab, setActiveTab] = useState<'curve' | 'performance' | 'fans'>('curve');
|
||||
const [fanState, setFanState] = useState<FanState | null>(null);
|
||||
const [activeDomain, setActiveDomain] = useState<'gpu' | 'memory'>('gpu');
|
||||
const [isProfileOpen, setIsProfileOpen] = useState(false);
|
||||
const profileRef = useRef<HTMLDivElement>(null);
|
||||
@@ -45,6 +49,16 @@ export default function App() {
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, [selectedGpuIndex, setActiveProfile]);
|
||||
|
||||
function refreshFans() {
|
||||
api.fans(selectedGpuIndex).then(setFanState).catch(console.error);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'fans') {
|
||||
refreshFans();
|
||||
}
|
||||
}, [activeTab, selectedGpuIndex]);
|
||||
|
||||
// Worst connection status wins
|
||||
const wsStatus =
|
||||
monitorWsStatus === 'disconnected' || curveWsStatus === 'disconnected'
|
||||
@@ -81,6 +95,12 @@ export default function App() {
|
||||
>
|
||||
Performance
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('fans')}
|
||||
className={`text-lg font-medium pb-2 -mb-[9px] border-b-2 transition-colors ${activeTab === 'fans' ? 'border-pink-500 text-zinc-100' : 'border-transparent text-zinc-500 hover:text-zinc-300'}`}
|
||||
>
|
||||
Fans
|
||||
</button>
|
||||
</div>
|
||||
<div className="relative" ref={profileRef}>
|
||||
<button
|
||||
@@ -126,12 +146,12 @@ export default function App() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="w-80 shrink-0 flex flex-col">
|
||||
<LiveMonitor monitor={monitor} history={monitorHistory} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{curve && (
|
||||
<div className="w-full">
|
||||
<PointTable
|
||||
@@ -142,7 +162,7 @@ export default function App() {
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
) : activeTab === 'performance' ? (
|
||||
<div className="flex gap-4 items-start w-full">
|
||||
<div className="flex-1 min-w-0">
|
||||
<PerformancePanel />
|
||||
@@ -151,6 +171,20 @@ export default function App() {
|
||||
<PerformanceMonitor monitor={monitor} history={monitorHistory} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex gap-4 items-start w-full">
|
||||
<div className="flex-1 min-w-0">
|
||||
<FanCurveEditor onChanged={refreshFans} />
|
||||
</div>
|
||||
<div className="w-80 shrink-0 flex flex-col">
|
||||
<FanMonitor
|
||||
monitor={monitor}
|
||||
history={monitorHistory}
|
||||
fanCurve={fanState?.curve ?? null}
|
||||
fanCurveActive={fanState?.curve_active ?? false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
Reference in new issue
Block a user