import { useState } from 'react'; import { useParams } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Box, Typography, Paper, Grid, Button, List, ListItem, ListItemText, ListItemButton, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Chip, FormControl, InputLabel, Select, MenuItem, Alert, Divider, } from '@mui/material'; import { Add as AddIcon, Delete as DeleteIcon, Edit as EditIcon, PlayArrow as PlayIcon, Stop as StopIcon, } from '@mui/icons-material'; import { fanCurvesApi, fanControlApi, serversApi } from '../utils/api'; import type { FanCurve, FanCurvePoint } from '../types'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Area } from 'recharts'; export default function FanCurves() { const { id } = useParams<{ id: string }>(); const serverId = parseInt(id || '0'); const queryClient = useQueryClient(); const [selectedCurve, setSelectedCurve] = useState(null); const [openDialog, setOpenDialog] = useState(false); const [editingCurve, setEditingCurve] = useState(null); const [formData, setFormData] = useState({ name: '', sensor_source: 'cpu', points: [ { temp: 30, speed: 10 }, { temp: 40, speed: 20 }, { temp: 50, speed: 35 }, { temp: 60, speed: 50 }, { temp: 70, speed: 70 }, { temp: 80, speed: 100 }, ] as FanCurvePoint[], }); const { data: server } = useQuery({ queryKey: ['server', serverId], queryFn: async () => { const response = await serversApi.getById(serverId); return response.data; }, }); const { data: curves } = useQuery({ queryKey: ['fan-curves', serverId], queryFn: async () => { const response = await fanCurvesApi.getAll(serverId); return response.data; }, }); const createMutation = useMutation({ mutationFn: (data: { name: string; curve_data: FanCurvePoint[]; sensor_source: string; is_active: boolean }) => fanCurvesApi.create(serverId, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['fan-curves', serverId] }); handleCloseDialog(); }, }); const updateMutation = useMutation({ mutationFn: ({ curveId, data }: { curveId: number; data: any }) => fanCurvesApi.update(serverId, curveId, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['fan-curves', serverId] }); handleCloseDialog(); }, }); const deleteMutation = useMutation({ mutationFn: (curveId: number) => fanCurvesApi.delete(serverId, curveId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['fan-curves', serverId] }); if (selectedCurve?.id) { setSelectedCurve(null); } }, }); const enableAutoMutation = useMutation({ mutationFn: (curveId: number) => fanControlApi.enableAuto(serverId, { enabled: true, curve_id: curveId }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['server', serverId] }); }, }); const disableAutoMutation = useMutation({ mutationFn: () => fanControlApi.disableAuto(serverId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['server', serverId] }); }, }); const handleOpenDialog = (curve?: FanCurve) => { if (curve) { setEditingCurve(curve); setFormData({ name: curve.name, sensor_source: curve.sensor_source, points: curve.curve_data, }); } else { setEditingCurve(null); setFormData({ name: '', sensor_source: 'cpu', points: [ { temp: 30, speed: 10 }, { temp: 40, speed: 20 }, { temp: 50, speed: 35 }, { temp: 60, speed: 50 }, { temp: 70, speed: 70 }, { temp: 80, speed: 100 }, ], }); } setOpenDialog(true); }; const handleCloseDialog = () => { setOpenDialog(false); setEditingCurve(null); }; const handleSubmit = () => { const data = { name: formData.name, curve_data: formData.points, sensor_source: formData.sensor_source, is_active: true, }; if (editingCurve) { updateMutation.mutate({ curveId: editingCurve.id, data }); } else { createMutation.mutate(data); } }; const updatePoint = (index: number, field: keyof FanCurvePoint, value: number) => { const newPoints = [...formData.points]; newPoints[index] = { ...newPoints[index], [field]: value }; setFormData({ ...formData, points: newPoints }); }; const addPoint = () => { setFormData({ ...formData, points: [...formData.points, { temp: 50, speed: 50 }], }); }; const removePoint = (index: number) => { if (formData.points.length > 2) { setFormData({ ...formData, points: formData.points.filter((_, i) => i !== index), }); } }; return ( Fan Curves {server?.name} {server?.auto_control_enabled ? ( ) : ( )} {server?.auto_control_enabled && ( Automatic fan control is currently active on this server. )} {/* Curve List */} {curves?.map((curve) => ( { e.stopPropagation(); handleOpenDialog(curve); }} > { e.stopPropagation(); if (confirm('Delete this fan curve?')) { deleteMutation.mutate(curve.id); } }} > } disablePadding > setSelectedCurve(curve)} > {server?.auto_control_enabled && selectedCurve?.id === curve.id && ( )} } /> ))} {!curves?.length && ( )} {/* Curve Preview */} {selectedCurve ? ( <> {selectedCurve.name} ({ ...p, label: `${p.temp}°C`, }))} margin={{ top: 5, right: 30, left: 20, bottom: 5 }} > [ name === 'speed' ? `${value}%` : `${value}°C`, name === 'speed' ? 'Fan Speed' : 'Temperature', ]} /> ) : ( Select a fan curve to preview )} {/* Create/Edit Dialog */} {editingCurve ? 'Edit Fan Curve' : 'Create Fan Curve'} setFormData({ ...formData, name: e.target.value })} margin="normal" /> Sensor Source Curve Points {formData.points.map((point, index) => ( updatePoint(index, 'temp', parseInt(e.target.value) || 0) } sx={{ flex: 1 }} /> updatePoint(index, 'speed', parseInt(e.target.value) || 0) } inputProps={{ min: 0, max: 100 }} sx={{ flex: 1 }} /> ))} Preview ); }