feat: add min/max temperature tracking for nodes

Track minimum and maximum CPU temperatures since monitoring started.
This provides better insight into temperature trends and cooling
adequacy over time.

Changes:
- Backend: Add CPUMin, CPUMaxRecord, MinRecorded, MaxRecorded fields
  to Temperature model
- Backend: Implement min/max tracking logic in monitoring cycle that
  preserves values across polling cycles
- Backend: Initialize min/max on first reading, update on extremes
- Frontend: Update Temperature TypeScript interface with new fields
- Frontend: Display min/max range in NodeCard tooltip (e.g., "52°C
  (48-67°C since monitoring started)")
- Frontend: Rebuild dist assets

Temperature display now shows:
- Current temperature with color coding (green/yellow/red)
- Tooltip with full min-max range and context
- Min/max tracked in-memory (resets on Pulse restart)

Example tooltip: "CPU: 52°C (48-67°C since monitoring started)"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Richard Courtman
2025-10-18 08:15:10 +00:00
parent 1c33531639
commit ae367a07be
7 changed files with 71 additions and 245 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -6,7 +6,7 @@
<meta name="theme-color" content="#000000" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<title>Pulse</title>
<script type="module" crossorigin src="/assets/index-cdElc049.js"></script>
<script type="module" crossorigin src="/assets/index-BFQdFd5E.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-VFB0BYLW.css">
</head>
<body>
@@ -133,6 +133,16 @@ const NodeCard: Component<NodeCardProps> = (props) => {
if (value === null) {
return 'CPU sensor unavailable';
}
const temp = props.node.temperature;
const hasMinMax = temp && temp.cpuMin > 0 && temp.cpuMaxRecord > 0;
if (hasMinMax) {
const min = Math.round(temp.cpuMin);
const max = Math.round(temp.cpuMaxRecord);
return `CPU: ${value}°C (${min}-${max}°C since monitoring started)`;
}
return `CPU: ${value}°C`;
});
+4
View File
@@ -512,6 +512,10 @@ export interface CPUInfo {
export interface Temperature {
cpuPackage?: number; // CPU package temperature (primary metric)
cpuMax?: number; // Highest core temperature
cpuMin?: number; // Minimum recorded CPU temperature (since monitoring started)
cpuMaxRecord?: number; // Maximum recorded CPU temperature (since monitoring started)
minRecorded?: string; // When minimum temperature was recorded
maxRecorded?: string; // When maximum temperature was recorded
cores?: CoreTemp[]; // Individual core temperatures
nvme?: NVMeTemp[]; // NVMe drive temperatures
available: boolean; // Whether any temperature data is available
+12 -8
View File
@@ -569,14 +569,18 @@ type CPUInfo struct {
// Temperature represents temperature sensors data
type Temperature struct {
CPUPackage float64 `json:"cpuPackage,omitempty"` // CPU package temperature (primary metric)
CPUMax float64 `json:"cpuMax,omitempty"` // Highest core temperature
Cores []CoreTemp `json:"cores,omitempty"` // Individual core temperatures
NVMe []NVMeTemp `json:"nvme,omitempty"` // NVMe drive temperatures
Available bool `json:"available"` // Whether any temperature data is available
HasCPU bool `json:"hasCPU"` // Whether CPU temperature data is available
HasNVMe bool `json:"hasNVMe"` // Whether NVMe temperature data is available
LastUpdate time.Time `json:"lastUpdate"` // When this data was collected
CPUPackage float64 `json:"cpuPackage,omitempty"` // CPU package temperature (primary metric)
CPUMax float64 `json:"cpuMax,omitempty"` // Highest core temperature
CPUMin float64 `json:"cpuMin,omitempty"` // Minimum recorded CPU temperature (since monitoring started)
CPUMaxRecord float64 `json:"cpuMaxRecord,omitempty"` // Maximum recorded CPU temperature (since monitoring started)
MinRecorded time.Time `json:"minRecorded,omitempty"` // When minimum temperature was recorded
MaxRecorded time.Time `json:"maxRecorded,omitempty"` // When maximum temperature was recorded
Cores []CoreTemp `json:"cores,omitempty"` // Individual core temperatures
NVMe []NVMeTemp `json:"nvme,omitempty"` // NVMe drive temperatures
Available bool `json:"available"` // Whether any temperature data is available
HasCPU bool `json:"hasCPU"` // Whether CPU temperature data is available
HasNVMe bool `json:"hasNVMe"` // Whether NVMe temperature data is available
LastUpdate time.Time `json:"lastUpdate"` // When this data was collected
}
// CoreTemp represents a CPU core temperature
+44
View File
@@ -2590,12 +2590,56 @@ func (m *Monitor) pollPVEInstance(ctx context.Context, instanceName string, clie
tempCancel()
if err == nil && temp != nil && temp.Available {
// Get the current CPU temperature (prefer package, fall back to max)
currentTemp := temp.CPUPackage
if currentTemp == 0 && temp.CPUMax > 0 {
currentTemp = temp.CPUMax
}
// Find previous temperature data for this node to preserve min/max
var prevTemp *models.Temperature
for _, prevNode := range prevInstanceNodes {
if prevNode.ID == modelNode.ID && prevNode.Temperature != nil {
prevTemp = prevNode.Temperature
break
}
}
// Initialize or update min/max tracking
if prevTemp != nil && prevTemp.CPUMin > 0 {
// Preserve existing min/max and update if necessary
temp.CPUMin = prevTemp.CPUMin
temp.CPUMaxRecord = prevTemp.CPUMaxRecord
temp.MinRecorded = prevTemp.MinRecorded
temp.MaxRecorded = prevTemp.MaxRecorded
// Update min if current is lower
if currentTemp > 0 && currentTemp < temp.CPUMin {
temp.CPUMin = currentTemp
temp.MinRecorded = time.Now()
}
// Update max if current is higher
if currentTemp > temp.CPUMaxRecord {
temp.CPUMaxRecord = currentTemp
temp.MaxRecorded = time.Now()
}
} else if currentTemp > 0 {
// First reading - initialize min/max to current value
temp.CPUMin = currentTemp
temp.CPUMaxRecord = currentTemp
temp.MinRecorded = time.Now()
temp.MaxRecorded = time.Now()
}
modelNode.Temperature = temp
log.Debug().
Str("node", node.Node).
Str("sshHost", sshHost).
Float64("cpuPackage", temp.CPUPackage).
Float64("cpuMax", temp.CPUMax).
Float64("cpuMin", temp.CPUMin).
Float64("cpuMaxRecord", temp.CPUMaxRecord).
Int("nvmeCount", len(temp.NVMe)).
Msg("Collected temperature data")
} else if err != nil {