Skip to content
Open
2 changes: 1 addition & 1 deletion api/api/versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"version": "v1",
"status": "active",
"release_date": "2025-11-02T13:45:06.273828+05:30",
"release_date": "2025-11-03T02:34:11.994065+05:30",
"end_of_life": "0001-01-01T00:00:00Z",
"changes": [
"Initial API version"
Expand Down
24 changes: 22 additions & 2 deletions api/internal/features/dashboard/system_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,9 @@ func parseLoadAverage(loadStr string) LoadStats {

func (m *DashboardMonitor) getCPUStats() CPUStats {
cpuStats := CPUStats{
Overall: 0.0,
PerCore: []CPUCore{},
Overall: 0.0,
PerCore: []CPUCore{},
Temperature: 0.0,
}

perCorePercent, err := cpu.Percent(time.Second, true)
Expand All @@ -190,6 +191,25 @@ func (m *DashboardMonitor) getCPUStats() CPUStats {
}
}

// Get CPU temperature using host.SensorsTemperatures
if temps, err := host.SensorsTemperatures(); err == nil && len(temps) > 0 {
// Calculate average temperature from all sensors
var totalTemp float64
var validReadings int

for _, temp := range temps {
// Filter for CPU-related sensors and valid temperature readings
if temp.Temperature > 0 && temp.Temperature < 150 { // Reasonable temperature range in Celsius
totalTemp += temp.Temperature
validReadings++
}
}

if validReadings > 0 {
cpuStats.Temperature = totalTemp / float64(validReadings)
}
}
Comment on lines +194 to +211
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Comment is misleading; consider filtering by sensor name.

The comment on line 201 states "Filter for CPU-related sensors" but the code only filters by temperature range (0-150°C) and doesn't distinguish between CPU, GPU, disk, or other thermal sensors. This can result in an average that includes non-CPU temperatures, making the "CPU Temperature" metric inaccurate.

Consider filtering by sensor name/type to only include CPU sensors:

 	for _, temp := range temps {
-		// Filter for CPU-related sensors and valid temperature readings
-		if temp.Temperature > 0 && temp.Temperature < 150 { // Reasonable temperature range in Celsius
+		// Filter for CPU-related sensors by name and valid temperature readings
+		sensorName := strings.ToLower(temp.SensorKey)
+		isCPUSensor := strings.Contains(sensorName, "cpu") || 
+		               strings.Contains(sensorName, "core") ||
+		               strings.Contains(sensorName, "proc")
+		if isCPUSensor && temp.Temperature > 0 && temp.Temperature < 150 {
 			totalTemp += temp.Temperature
 			validReadings++
 		}
 	}

You'll need to add "strings" to the imports if not already present.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In api/internal/features/dashboard/system_stats.go around lines 194 to 211, the
code claims to "Filter for CPU-related sensors" but only filters by temperature
range, so non-CPU sensors may be included; update the loop to also filter sensor
names (e.g., check temp.SensorKey or temp.SensorKey/Label contains substrings
like "cpu", "core", "package" case-insensitively) before aggregating, and add
"strings" to the imports if not present; ensure the name check is
case-insensitive and still falls back to the temperature-range check so only
relevant CPU sensors are averaged.


return cpuStats
}

Expand Down
5 changes: 3 additions & 2 deletions api/internal/features/dashboard/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ type CPUCore struct {
}

type CPUStats struct {
Overall float64 `json:"overall"`
PerCore []CPUCore `json:"per_core"`
Overall float64 `json:"overall"`
PerCore []CPUCore `json:"per_core"`
Temperature float64 `json:"temperature"`
}

type MemoryStats struct {
Expand Down
121 changes: 121 additions & 0 deletions view/app/dashboard/components/system/cpu-temperature.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use client';

import React from 'react';
import { Thermometer } from 'lucide-react';
import { SystemStatsType } from '@/redux/types/monitor';
import { TypographySmall, TypographyMuted } from '@/components/ui/typography';
import { SystemMetricCard } from './system-metric-card';
import { useSystemMetric } from '../../hooks/use-system-metric';
import { DEFAULT_METRICS } from '../utils/constants';
import { CPUTemperatureCardSkeletonContent } from './skeletons/cpu-temperature';

interface CPUTemperatureCardProps {
systemStats: SystemStatsType | null;
}

interface CPUTemperatureDisplayProps {
temperature: number;
label: string;
}

interface TemperatureGaugeProps {
temperature: number;
}

const getTemperatureColor = (temp: number): string => {
if (temp < 50) return 'text-blue-500';
if (temp < 70) return 'text-green-500';
if (temp < 85) return 'text-orange-500';
return 'text-red-500';
};

const getTemperatureStatus = (temp: number): string => {
if (temp < 50) return 'Cool';
if (temp < 70) return 'Normal';
if (temp < 85) return 'Warm';
return 'Hot';
};

const CPUTemperatureDisplay: React.FC<CPUTemperatureDisplayProps> = ({ temperature, label }) => {
const tempColor = getTemperatureColor(temperature);
const status = getTemperatureStatus(temperature);

return (
<div className="text-center">
<TypographyMuted className="text-xs">{label}</TypographyMuted>
<div className={`text-4xl font-bold ${tempColor} mt-1`}>
{temperature > 0 ? `${temperature.toFixed(1)}°C` : 'N/A'}
</div>
{temperature > 0 && (
<TypographyMuted className="text-xs mt-1">{status}</TypographyMuted>
)}
</div>
);
};

const TemperatureGauge: React.FC<TemperatureGaugeProps> = ({ temperature }) => {
// Calculate percentage based on 0-100°C range
const percentage = Math.min((temperature / 100) * 100, 100);
const gaugeColor = getTemperatureColor(temperature);

return (
<div className="space-y-2">
<div className="flex justify-between text-xs text-muted-foreground">
<span>0°C</span>
<span>50°C</span>
<span>100°C</span>
</div>
<div className="w-full bg-secondary rounded-full h-4 overflow-hidden">
<div
className={`h-full ${gaugeColor} transition-all duration-300 rounded-full`}
style={{
width: `${percentage}%`,
backgroundColor: 'currentColor'
}}
/>
</div>
<div className="flex justify-between text-xs">
<TypographyMuted>Min: 0°C</TypographyMuted>
<TypographyMuted>Max: {temperature > 0 ? `${temperature.toFixed(1)}°C` : 'N/A'}</TypographyMuted>
</div>
</div>
);
};

const CPUTemperatureCard: React.FC<CPUTemperatureCardProps> = ({ systemStats }) => {
const {
data: cpu,
isLoading,
t
} = useSystemMetric({
systemStats,
extractData: (stats) => stats.cpu,
defaultData: DEFAULT_METRICS.cpu
});

const temperature = cpu.temperature || 0;

return (
<SystemMetricCard
title={t('dashboard.cpu.temperature')}
icon={Thermometer}
isLoading={isLoading}
skeletonContent={<CPUTemperatureCardSkeletonContent />}
>
<div className="space-y-6">
<CPUTemperatureDisplay temperature={temperature} label={t('dashboard.cpu.current_temp')} />
<TemperatureGauge temperature={temperature} />
{temperature === 0 && (
<div className="text-center">
<TypographySmall className="text-muted-foreground">
Temperature sensors not available on this system
</TypographySmall>
</div>
)}
</div>
</SystemMetricCard>
);
};

export default CPUTemperatureCard;

53 changes: 53 additions & 0 deletions view/app/dashboard/components/system/skeletons/cpu-temperature.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use client';

import React from 'react';
import { Thermometer } from 'lucide-react';
import { Skeleton } from '@/components/ui/skeleton';
import { TypographyMuted } from '@/components/ui/typography';
import { SystemMetricCard } from '../system-metric-card';
import { useSystemMetric } from '../../../hooks/use-system-metric';
import { DEFAULT_METRICS } from '../../utils/constants';

export function CPUTemperatureCardSkeletonContent() {
return (
<div className="space-y-6">
<div className="text-center">
<TypographyMuted className="text-xs">Current Temperature</TypographyMuted>
<Skeleton className="h-12 w-32 mx-auto mt-1" />
<Skeleton className="h-4 w-16 mx-auto mt-1" />
</div>
<div className="space-y-2">
<div className="flex justify-between text-xs text-muted-foreground">
<span>0°C</span>
<span>50°C</span>
<span>100°C</span>
</div>
<Skeleton className="h-4 w-full rounded-full" />
<div className="flex justify-between">
<Skeleton className="h-3 w-16" />
<Skeleton className="h-3 w-16" />
</div>
</div>
</div>
);
}
Comment on lines +11 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use translation key instead of hardcoded text.

Line 15 hardcodes "Current Temperature" while the actual component uses t('dashboard.cpu.current_temp'). This creates an inconsistency between the skeleton and loaded states.

Apply this diff:

-export function CPUTemperatureCardSkeletonContent() {
+export function CPUTemperatureCardSkeletonContent({ label }: { label: string }) {
   return (
     <div className="space-y-6">
       <div className="text-center">
-        <TypographyMuted className="text-xs">Current Temperature</TypographyMuted>
+        <TypographyMuted className="text-xs">{label}</TypographyMuted>
         <Skeleton className="h-12 w-32 mx-auto mt-1" />

Then pass the translated label from CPUTemperatureCardSkeleton:

   return (
     <SystemMetricCard
       title={t('dashboard.cpu.temperature')}
       icon={Thermometer}
       isLoading={true}
-      skeletonContent={<CPUTemperatureCardSkeletonContent />}
+      skeletonContent={<CPUTemperatureCardSkeletonContent label={t('dashboard.cpu.current_temp')} />}
     >

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In view/app/dashboard/components/system/skeletons/cpu-temperature.tsx around
lines 11 to 33, the skeleton hardcodes "Current Temperature"; change it to
accept a translated label prop (e.g., label: string) and render that instead of
the literal string, then update the component's callers
(CPUTemperatureCardSkeleton / parent) to pass t('dashboard.cpu.current_temp')
into the skeleton's label prop so the skeleton and loaded state use the same
translation key.


export function CPUTemperatureCardSkeleton() {
const { t } = useSystemMetric({
systemStats: null,
extractData: (stats) => stats.cpu,
defaultData: DEFAULT_METRICS.cpu
});

return (
<SystemMetricCard
title={t('dashboard.cpu.temperature')}
icon={Thermometer}
isLoading={true}
skeletonContent={<CPUTemperatureCardSkeletonContent />}
>
<div />
</SystemMetricCard>
);
}

3 changes: 2 additions & 1 deletion view/app/dashboard/components/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export const DEFAULT_METRICS = {
},
cpu: {
overall: 0 as number,
per_core: [] as Array<{ core_id: number; usage: number }>
per_core: [] as Array<{ core_id: number; usage: number }>,
temperature: 0 as number
},
memory: {
total: 0 as number,
Expand Down
9 changes: 8 additions & 1 deletion view/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ContainersWidget from './components/containers/containers-widget';
import SystemInfoCard from './components/system/system-info';
import LoadAverageCard from './components/system/load-average';
import CPUUsageCard from './components/system/cpu-usage';
import CPUTemperatureCard from './components/system/cpu-temperature';
import MemoryUsageCard from './components/system/memory-usage';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Package, ArrowRight, RefreshCw, Info } from 'lucide-react';
Expand Down Expand Up @@ -44,7 +45,7 @@ function DashboardPage() {
handleLayoutChange
} = useDashboard();

const defaultHiddenWidgets = ['clock', 'network'];
const defaultHiddenWidgets = ['clock', 'network', 'cpu-temperature'];

const [hiddenWidgets, setHiddenWidgets] = React.useState<string[]>(defaultHiddenWidgets);

Expand Down Expand Up @@ -89,6 +90,7 @@ function DashboardPage() {
{ id: 'network', label: 'Network Traffic' },
{ id: 'load-average', label: 'Load Average' },
{ id: 'cpu-usage', label: 'CPU Usage' },
{ id: 'cpu-temperature', label: 'CPU Temperature' },
{ id: 'memory-usage', label: 'Memory Usage' },
{ id: 'disk-usage', label: 'Disk Usage' },
{ id: 'containers', label: 'Containers' }
Expand Down Expand Up @@ -255,6 +257,11 @@ const MonitoringSection = ({
component: <CPUUsageCard systemStats={systemStats} />,
isDefault: true
},
{
id: 'cpu-temperature',
component: <CPUTemperatureCard systemStats={systemStats} />,
isDefault: false
},
{
id: 'memory-usage',
component: <MemoryUsageCard systemStats={systemStats} />,
Expand Down
4 changes: 3 additions & 1 deletion view/lib/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,9 @@
"overall": "Overall",
"usage": "Usage (%)",
"cores": "Cores",
"perCore": "Per Core"
"perCore": "Per Core",
"temperature": "CPU Temperature",
"current_temp": "Current Temperature"
},
"memory": {
"title": "Memory Usage",
Expand Down
4 changes: 3 additions & 1 deletion view/lib/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,9 @@
"overall": "General",
"usage": "Uso (%)",
"cores": "Núcleos",
"perCore": "Por núcleo"
"perCore": "Por núcleo",
"temperature": "Temperatura de CPU",
"current_temp": "Temperatura Actual"
},
"memory": {
"title": "Uso de memoria",
Expand Down
4 changes: 3 additions & 1 deletion view/lib/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,9 @@
"overall": "Global",
"usage": "Utilisation (%)",
"cores": "Cœurs",
"perCore": "Par cœur"
"perCore": "Par cœur",
"temperature": "Température du CPU",
"current_temp": "Température Actuelle"
},
"memory": {
"title": "Utilisation de la mémoire",
Expand Down
4 changes: 3 additions & 1 deletion view/lib/i18n/locales/kn.json
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,9 @@
"overall": "ಒಟ್ಟಾರೆ",
"usage": "ಬಳಕೆ (%)",
"cores": "ಕೋರ್‌ಗಳು",
"perCore": "ಪ್ರತಿ ಕೋರ್"
"perCore": "ಪ್ರತಿ ಕೋರ್",
"temperature": "CPU ತಾಪಮಾನ",
"current_temp": "ಪ್ರಸ್ತುತ ತಾಪಮಾನ"
},
"memory": {
"title": "ಮೆಮೊರಿ ಬಳಕೆ",
Expand Down
4 changes: 3 additions & 1 deletion view/lib/i18n/locales/ml.json
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,9 @@
"overall": "മൊത്തത്തിൽ",
"usage": "ഉപയോഗം (%)",
"cores": "കോറുകൾ",
"perCore": "ഓരോ കോറിനും"
"perCore": "ഓരോ കോറിനും",
"temperature": "CPU താപനില",
"current_temp": "നിലവിലെ താപനില"
},
"memory": {
"title": "മെമ്മറി ഉപയോഗം",
Expand Down
1 change: 1 addition & 0 deletions view/redux/types/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export interface CPUCore {
export interface CPUStats {
overall: number;
per_core: CPUCore[];
temperature: number;
}

export interface NetworkInterface {
Expand Down
Loading