Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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