- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 70
 
feat: enhance dashboard with draggable layout, charts, and extended system metrics #536
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| 
          
 Caution Review failedThe pull request is closed. WalkthroughThe dashboard undergoes a comprehensive redesign introducing draggable grid layouts for system statistics cards, enhanced metric visualizations via bar and doughnut charts, and expanded backend system data collection including hostname, kernel version, architecture, and per-core CPU metrics. Changes
 Sequence DiagramsequenceDiagram
    participant User
    participant Dashboard as Dashboard Page
    participant Hook as useDashboard
    participant API as API
    participant DragGrid as DraggableGrid
    participant LS as localStorage
    User->>Dashboard: Load dashboard
    Dashboard->>Hook: Call useDashboard()
    Hook->>API: Fetch system stats, containers, SMTP config
    API-->>Hook: Return data
    Hook->>LS: Check for saved layout order
    Hook-->>Dashboard: Return data + UI state + actions
    Dashboard->>Dashboard: Mount with layoutResetKey
    Dashboard->>DragGrid: Render with items + storageKey
    DragGrid->>LS: Load saved item order
    LS-->>DragGrid: Return saved order or undefined
    DragGrid->>DragGrid: Merge saved order with items
    
    Note over User,DragGrid: User drags item
    User->>DragGrid: Drag and drop item
    DragGrid->>DragGrid: Update local order
    DragGrid->>LS: Persist new order
    DragGrid->>Dashboard: Call onLayoutChange callback
    
    Note over User,Dashboard: User clicks Reset Layout
    User->>Dashboard: Click reset button
    Dashboard->>Hook: Call handleResetLayout()
    Hook->>LS: Clear saved order + dismiss hint
    Hook->>Dashboard: Return new layoutResetKey
    Dashboard->>DragGrid: Re-render with new resetKey
    DragGrid->>LS: Load (finds none, uses defaults)
    DragGrid->>DragGrid: Reset to default item order
    Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention: 
 Possibly related PRs
 Poem
 Pre-merge checks and finishing touches❌ Failed checks (1 warning)
 ✅ Passed checks (2 passed)
 📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (9)
 Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment   | 
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️  Outside diff range comments (2)
api/internal/features/dashboard/init.go (1)
31-55: Restart logic uses a canceled context (will not actually restart)
UpdateConfig/SetOperationscallStop()thenStart(), butStart()reusesm.ctxcreated in the constructor. Aftercancel(), the context remains canceled and the loop exits immediately. Create a fresh context inStart()(or a helper) before spawning the goroutine.Minimal fix sketch:
func (m *DashboardMonitor) Start() { - go func() { + // refresh context for a new run + if m.cancel != nil { + // no-op if already fresh + } + m.ctx, m.cancel = context.WithCancel(context.Background()) + go func() { ticker := time.NewTicker(m.Interval) ... }() }Also ensure
Stop()is idempotent and doesn’t double-close.Also applies to: 65-74, 89-98
view/app/dashboard/hooks/use-monitor.ts (1)
109-115: Duplicate reconnect timers and untracked timeout can cause double starts and leaks.On error you call setTimeout(startMonitoring, 5000) and you also have a reconnect effect that schedules another timer via reconnectTimeoutRef. This can fire twice and the untracked timeout cannot be cleared on unmount.
Apply this diff to rely solely on the tracked reconnect effect:
if (isMonitoring) { stopMonitoring(); - setTimeout(startMonitoring, 5000); }
🧹 Nitpick comments (11)
view/app/dashboard/components/charts/time-series-chart.tsx (1)
29-35: Don’t hardcode 'en-US' for time labelsUse the viewer’s locale (or a prop) to localize time on the X-axis.
Apply this diff:
- const chartData = data.map(point => ({ - time: new Date(point.timestamp).toLocaleTimeString('en-US', { + const chartData = data.map(point => ({ + time: new Date(point.timestamp).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' }), [dataKey]: point.value }));view/app/dashboard/components/charts/caddy-charts.tsx (1)
13-41: Localize titles/descriptionsHook these strings to i18n (e.g., useTranslations) instead of hardcoding English so the new locale keys are actually used.
Example:
+'use client'; +import { useTranslations } from 'next-intl'; export function CaddyCharts({ data }: CaddyChartsProps) { - return ( + const t = useTranslations('dashboard.caddy'); // or another namespace + return ( <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <TimeSeriesChart - title="Request Rate" - description="HTTP requests per second" + title={t('requestRateTitle')} + description={t('requestRateDesc')} data={data.requestRate} dataKey="requests" color="hsl(var(--chart-1))" unit=" req/s" formatValue={(value) => `${value.toFixed(2)} req/s`} /> <TimeSeriesChart - title="Error Rate" - description="Percentage of failed requests" + title={t('errorRateTitle')} + description={t('errorRateDesc')} data={data.errorRate} dataKey="errors" color="hsl(var(--destructive))" unit="%" formatValue={(value) => `${value.toFixed(2)}%`} /> <TimeSeriesChart - title="Average Latency" - description="Response time in milliseconds" + title={t('avgLatencyTitle')} + description={t('avgLatencyDesc')} data={data.avgLatency} dataKey="latency" color="hsl(var(--chart-3))" unit="ms" formatValue={(value) => `${value.toFixed(0)}ms`} /> </div> ); }Add the corresponding keys in locales.
view/app/dashboard/components/charts/server-specific-charts.tsx (1)
30-33: Consider simplifying the sign prefix logic.The conditional sign prefix can be simplified using a ternary or by relying on JavaScript's default number-to-string conversion with explicit sign handling.
Consider this alternative:
formatValue={(value) => { - const sign = value >= 0 ? '+' : ''; - return `${sign}${value.toFixed(3)} GB/hr`; + return `${value >= 0 ? '+' : ''}${value.toFixed(3)} GB/hr`; }}view/app/dashboard/components/system/caddy-metrics.tsx (2)
57-59: Consider sorting applications before displaying "top 5".The code displays the first 5 applications from
perApplicationwithout any sorting, making the selection arbitrary. Consider sorting by a relevant metric (e.g., request rate, error rate) to show the most important applications.Apply this diff to sort by request rate:
{Object.entries(caddyMetrics.perApplication) + .sort(([, a], [, b]) => b.requestRate - a.requestRate) .slice(0, 5) .map(([domain, metrics]) => (
70-73: Document or make configurable the error rate threshold.The 5% error rate threshold for highlighting errors in red is hardcoded. Consider extracting this to a constant or making it configurable for better maintainability.
+const ERROR_RATE_THRESHOLD = 5; + const CaddyMetricsCard: React.FC<CaddyMetricsCardProps> = ({ caddyMetrics }) => { // ... <span - className={`text-xs ${metrics.errorRate > 5 ? 'text-red-500' : 'text-muted-foreground'}`} + className={`text-xs ${metrics.errorRate > ERROR_RATE_THRESHOLD ? 'text-red-500' : 'text-muted-foreground'}`} >view/app/dashboard/hooks/use-monitor.ts (2)
31-31: Use ReturnType in client code.NodeJS.Timeout is Node-specific; in the browser it’s a number. Use a cross‑env type to avoid TS friction in Next.js client modules.
- const reconnectTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined); + const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
91-108: DRY up capped append logic for time-series.The slice-and-append pattern repeats. Extract a tiny helper to reduce duplication and off‑by‑one risk.
+ const appendPoint = (arr: TimeSeriesDataPoint[], p: TimeSeriesDataPoint) => + [...arr.slice(-MAX_HISTORY_POINTS + 1), p]; ... - setAdvancedTimeSeries(prev => { - const newData = { ...prev }; - if (data.pageFaults?.faultsPerSec !== undefined) { - newData.pageFaultsPerSec = [ - ...prev.pageFaultsPerSec.slice(-MAX_HISTORY_POINTS + 1), - { timestamp, value: data.pageFaults.faultsPerSec } - ]; - } - if (data.disk?.growthRate !== undefined) { - newData.diskGrowthRate = [ - ...prev.diskGrowthRate.slice(-MAX_HISTORY_POINTS + 1), - { timestamp, value: data.disk.growthRate } - ]; - } - return newData; - }); + setAdvancedTimeSeries(prev => ({ + pageFaultsPerSec: data.pageFaults?.faultsPerSec !== undefined + ? appendPoint(prev.pageFaultsPerSec, { timestamp, value: data.pageFaults.faultsPerSec }) + : prev.pageFaultsPerSec, + diskGrowthRate: data.disk?.growthRate !== undefined + ? appendPoint(prev.diskGrowthRate, { timestamp, value: data.disk.growthRate }) + : prev.diskGrowthRate + })); ... - setCaddyTimeSeries(prev => ({ - requestRate: [ - ...prev.requestRate.slice(-MAX_HISTORY_POINTS + 1), - { timestamp, value: data.overall.requestRate } - ], - errorRate: [ - ...prev.errorRate.slice(-MAX_HISTORY_POINTS + 1), - { timestamp, value: data.overall.errorRate } - ], - avgLatency: [ - ...prev.avgLatency.slice(-MAX_HISTORY_POINTS + 1), - { timestamp, value: data.overall.avgLatency } - ] - })); + setCaddyTimeSeries(prev => ({ + requestRate: appendPoint(prev.requestRate, { timestamp, value: data.overall.requestRate }), + errorRate: appendPoint(prev.errorRate, { timestamp, value: data.overall.errorRate }), + avgLatency: appendPoint(prev.avgLatency, { timestamp, value: data.overall.avgLatency }) + }));Also applies to: 64-86
api/internal/features/dashboard/caddy_metrics.go (2)
64-82: Prometheus text parsing is fragile; prefer expfmt.Manual splitting breaks with escaped quotes, commas in label values, exemplars, or metadata lines. Use the official parser.
Illustrative replacement:
import ( "github.com/prometheus/common/expfmt" io_prom "github.com/prometheus/client_model/go" ) func (m *DashboardMonitor) fetchCaddyMetrics(url string) (map[string]*io_prom.MetricFamily, error) { // http GET same as today... var parser expfmt.TextParser fam, err := parser.TextToMetricFamilies(bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("parse metrics: %w", err) } return fam, nil }Then iterate metric families to aggregate safely. This also gives you sum/count directly for histograms/summaries.
Also applies to: 85-121
41-62: Reuse an http.Client or use context.WithTimeout.Creating a new client per call is fine at low rates but avoid per-scrape allocation; also add User-Agent for observability.
- client := &http.Client{ - Timeout: 5 * time.Second, - } - resp, err := client.Get(url) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + req.Header.Set("User-Agent", "nixopus-dashboard/1.0") + resp, err := http.DefaultClient.Do(req)api/internal/features/dashboard/types.go (2)
109-122: Document units for Caddy metrics fields.Clarify units to stay consistent with UI (e.g., RequestRate req/s, ErrorRate %, AvgLatency ms).
type CaddyAppMetrics struct { - RequestRate float64 `json:"requestRate"` // req/second - ErrorRate float64 `json:"errorRate"` // percentage of 4xx/5xx responses - AvgLatency float64 `json:"avgLatency"` // milliseconds + RequestRate float64 `json:"requestRate"` // requests per second (rps) + ErrorRate float64 `json:"errorRate"` // error percentage over window [0..100] + AvgLatency float64 `json:"avgLatency"` // average latency in milliseconds over window RequestCount uint64 `json:"requestCount"` Error4xxCount uint64 `json:"error4xxCount"` Error5xxCount uint64 `json:"error5xxCount"` }
85-94: Clarify GrowthRate units in JSON contract.Make the GB/hr unit explicit in the struct tag comment to help the UI.
type DiskStats struct { Total float64 `json:"total"` Used float64 `json:"used"` Available float64 `json:"available"` Percentage float64 `json:"percentage"` MountPoint string `json:"mountPoint"` AllMounts []DiskMount `json:"allMounts"` - GrowthRate float64 `json:"growthRate"` // GB/hr + GrowthRate float64 `json:"growthRate"` // disk growth rate in GB per hour }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
.gitignore(1 hunks)api/Dockerfile.dev(1 hunks)api/internal/features/dashboard/caddy_metrics.go(1 hunks)api/internal/features/dashboard/init.go(1 hunks)api/internal/features/dashboard/system_stats.go(3 hunks)api/internal/features/dashboard/types.go(3 hunks)view/app/dashboard/components/charts/caddy-charts.tsx(1 hunks)view/app/dashboard/components/charts/server-specific-charts.tsx(1 hunks)view/app/dashboard/components/charts/time-series-chart.tsx(1 hunks)view/app/dashboard/components/system/caddy-metrics.tsx(1 hunks)view/app/dashboard/components/system/cpu-cores.tsx(1 hunks)view/app/dashboard/components/system/server-metrics.tsx(1 hunks)view/app/dashboard/components/system/system-stats.tsx(3 hunks)view/app/dashboard/hooks/use-monitor.ts(4 hunks)view/app/dashboard/page.tsx(5 hunks)view/lib/i18n/locales/en.json(1 hunks)view/lib/i18n/locales/es.json(1 hunks)view/lib/i18n/locales/fr.json(1 hunks)view/lib/i18n/locales/kn.json(1 hunks)view/lib/i18n/locales/ml.json(1 hunks)view/redux/types/monitor.ts(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (13)
api/internal/features/dashboard/init.go (1)
api/internal/features/dashboard/types.go (1)
GetCaddyMetrics(20-20)
view/app/dashboard/components/charts/caddy-charts.tsx (2)
view/app/dashboard/hooks/use-monitor.ts (1)
CaddyTimeSeriesData(13-17)view/app/dashboard/components/charts/time-series-chart.tsx (1)
TimeSeriesChart(18-106)
view/app/dashboard/components/system/caddy-metrics.tsx (1)
view/redux/types/monitor.ts (1)
CaddyMetrics(83-87)
view/app/dashboard/components/system/server-metrics.tsx (2)
view/components/ui/card.tsx (4)
Card(58-58)CardHeader(58-58)CardTitle(58-58)CardContent(58-58)view/components/ui/skeleton.tsx (1)
Skeleton(13-13)
view/app/dashboard/hooks/use-monitor.ts (2)
view/hooks/socket-provider.tsx (1)
useWebSocket(190-198)view/redux/types/monitor.ts (3)
ContainerData(1-18)SystemStatsType(89-98)CaddyMetrics(83-87)
view/app/dashboard/components/charts/time-series-chart.tsx (1)
view/app/dashboard/hooks/use-monitor.ts (1)
TimeSeriesDataPoint(8-11)
api/internal/features/dashboard/caddy_metrics.go (2)
api/internal/features/dashboard/types.go (4)
DashboardMonitor(34-49)GetCaddyMetrics(20-20)CaddyMetrics(109-113)CaddyAppMetrics(115-122)api/internal/features/logger/init.go (1)
Error(22-22)
api/internal/features/dashboard/system_stats.go (1)
api/internal/features/dashboard/types.go (3)
DashboardMonitor(34-49)CPUCoreStats(95-101)PageFaultStats(103-107)
view/app/dashboard/components/system/system-stats.tsx (2)
view/app/dashboard/components/system/cpu-cores.tsx (1)
CPUCoresCardSkeleton(48-67)view/app/dashboard/components/system/server-metrics.tsx (1)
ServerMetricsCardSkeleton(79-94)
view/app/dashboard/components/charts/server-specific-charts.tsx (2)
view/app/dashboard/hooks/use-monitor.ts (1)
ServerSpecificTimeSeriesData(19-22)view/app/dashboard/components/charts/time-series-chart.tsx (1)
TimeSeriesChart(18-106)
view/app/dashboard/components/system/cpu-cores.tsx (1)
view/redux/types/monitor.ts (1)
CPUCoreStats(60-66)
view/app/dashboard/page.tsx (2)
view/app/dashboard/components/charts/caddy-charts.tsx (1)
CaddyCharts(10-44)view/app/dashboard/components/charts/server-specific-charts.tsx (1)
ServerSpcificCharts(10-37)
api/internal/features/dashboard/types.go (1)
api/internal/features/deploy/docker/init.go (1)
DockerService(22-26)
🔇 Additional comments (13)
api/Dockerfile.dev (1)
5-5: Justify the addition of openssh-client and verify necessity.The addition of
openssh-clientto the development image lacks context. While the change itself is syntactically correct, it increases the image size and dependencies without documented justification.Please clarify:
- Why is
 openssh-clientneeded in the development environment?- Is it actively used during local development or CI/CD?
 - Could this dependency be added conditionally or deferred to a separate build stage?
 Additionally, the PR objectives describe adding Server/HTTP metrics to the dashboard, but this file change appears orthogonal to that scope. Please verify that this Dockerfile change is intentionally part of this metrics PR or if it's an unrelated dependency.
.gitignore (1)
70-70: LGTM!Adding
api/nixopus-apito the ignore list is appropriate and aligns with the PR objective of adding API-related metrics infrastructure.view/lib/i18n/locales/es.json (1)
769-772: LGTM: dashboard.charts keys addedTranslations are clear and consistent with other locales. No issues.
view/lib/i18n/locales/fr.json (1)
770-773: LGTM: dashboard.charts labels (FR)Accurate and consistent. Nothing to fix.
view/lib/i18n/locales/kn.json (1)
766-769: LGTM: dashboard.charts labels (KN)Additions align with other locales.
view/lib/i18n/locales/en.json (1)
786-789: LGTM: dashboard.charts labels (EN)Keys and copy look good.
view/lib/i18n/locales/ml.json (1)
785-788: LGTM: dashboard.charts labels (ML)Additions are consistent with other locales.
api/internal/features/dashboard/init.go (1)
82-83: Wire-up verified; all checks pass✓ GetCaddyMetrics is a proper
DashboardOperationconstant (types.go:20) included inAllOperations(types.go:26)
✓(*DashboardMonitor).GetCaddyMetrics()exists (caddy_metrics.go:23) and is non-blocking—broadcasts metrics then returns
✓ Switch case at lines 82-83 is correctly wiredview/app/dashboard/components/system/system-stats.tsx (1)
8-9: LGTM! Clean integration of new components.The new CPU cores and server metrics components are properly integrated with both their active and skeleton states, following the established pattern in the file.
Also applies to: 22-23, 33-34
view/app/dashboard/page.tsx (1)
12-12: Update import after fixing component name typo.This import references the misspelled component name
ServerSpcificCharts. Once the typo inserver-specific-charts.tsxis corrected, update this import accordingly.This comment is related to the typo identified in
view/app/dashboard/components/charts/server-specific-charts.tsxline 10.view/redux/types/monitor.ts (1)
57-97: LGTM! Well-structured type definitions.The new type definitions are clear, properly typed, and follow TypeScript best practices. The optional fields are appropriately marked, and the interfaces are cohesive.
view/app/dashboard/components/system/cpu-cores.tsx (1)
14-46: LGTM! Clean implementation with proper null checks.The component correctly handles edge cases (null/empty data) and provides a clear visualization of per-core CPU metrics.
api/internal/features/dashboard/system_stats.go (1)
231-251: LGTM! Clean disk growth rate calculation.The disk growth rate calculation correctly handles the initial state, checks for zero elapsed time, and maintains the necessary state for tracking changes over time.
        
          
                view/app/dashboard/components/charts/server-specific-charts.tsx
              
                Outdated
          
            Show resolved
            Hide resolved
        
      | const MonitoringSection = ({ | ||
| systemStats, | ||
| containersData, | ||
| caddyMetrics, | ||
| caddyTimeSeries, | ||
| advancedTimeSeries, | ||
| t | ||
| }: { | ||
| systemStats: any; | ||
| containersData: any; | ||
| caddyMetrics: any; | ||
| caddyTimeSeries: any; | ||
| advancedTimeSeries: any; | ||
| t: any; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Replace any types with proper type definitions.
The MonitoringSection component uses any types for its props, which defeats the purpose of TypeScript's type safety. Consider defining a proper interface or using the existing types.
Apply this diff to add proper types:
+import { SystemStatsType } from '@/redux/types/monitor';
+import { ContainerData } from '@/redux/types/monitor';
+import { CaddyMetrics } from '@/redux/types/monitor';
+import { CaddyTimeSeriesData, ServerSpecificTimeSeriesData } from './hooks/use-monitor';
+
+interface MonitoringSectionProps {
+  systemStats: SystemStatsType | null;
+  containersData: ContainerData[];
+  caddyMetrics: CaddyMetrics | null;
+  caddyTimeSeries: CaddyTimeSeriesData;
+  advancedTimeSeries: ServerSpecificTimeSeriesData;
+  t: (key: string) => string;
+}
+
 const MonitoringSection = ({
   systemStats,
   containersData,
   caddyMetrics,
   caddyTimeSeries,
   advancedTimeSeries,
   t
-}: {
-  systemStats: any;
-  containersData: any;
-  caddyMetrics: any;
-  caddyTimeSeries: any;
-  advancedTimeSeries: any;
-  t: any;
-}) => {
+}: MonitoringSectionProps) => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const MonitoringSection = ({ | |
| systemStats, | |
| containersData, | |
| caddyMetrics, | |
| caddyTimeSeries, | |
| advancedTimeSeries, | |
| t | |
| }: { | |
| systemStats: any; | |
| containersData: any; | |
| caddyMetrics: any; | |
| caddyTimeSeries: any; | |
| advancedTimeSeries: any; | |
| t: any; | |
| import { SystemStatsType } from '@/redux/types/monitor'; | |
| import { ContainerData } from '@/redux/types/monitor'; | |
| import { CaddyMetrics } from '@/redux/types/monitor'; | |
| import { CaddyTimeSeriesData, ServerSpecificTimeSeriesData } from './hooks/use-monitor'; | |
| interface MonitoringSectionProps { | |
| systemStats: SystemStatsType | null; | |
| containersData: ContainerData[]; | |
| caddyMetrics: CaddyMetrics | null; | |
| caddyTimeSeries: CaddyTimeSeriesData; | |
| advancedTimeSeries: ServerSpecificTimeSeriesData; | |
| t: (key: string) => string; | |
| } | |
| const MonitoringSection = ({ | |
| systemStats, | |
| containersData, | |
| caddyMetrics, | |
| caddyTimeSeries, | |
| advancedTimeSeries, | |
| t | |
| }: MonitoringSectionProps) => { | 
🤖 Prompt for AI Agents
In view/app/dashboard/page.tsx around lines 74 to 87, the MonitoringSection
props are typed as `any`; define a proper props interface (e.g.,
MonitoringSectionProps) that replaces `any` with concrete types (use existing
types from your domain if available: SystemStats, ContainerData[], CaddyMetrics,
TimeSeriesPoint[]/TimeSeries, AdvancedTimeSeries, and a translation function
type like TFunction from i18next), import those types or declare them locally,
then update the component signature to use the new interface and export/consume
it accordingly; ensure arrays and nullable/optional fields are typed correctly
and adjust call sites if needed.
6d5becb    to
    1e559fd      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
view/app/dashboard/page.tsx (1)
126-136: Replaceanytypes with proper type definitions.The
MonitoringSectioncomponent still usesanytypes for its props, defeating TypeScript's type safety. This was flagged in a previous review and remains unaddressed.Apply this diff to add proper types:
+import { SystemStatsType } from '@/redux/types/monitor'; +import { ContainerData } from '@/redux/types/monitor'; + +interface MonitoringSectionProps { + systemStats: SystemStatsType | null; + containersData: ContainerData[]; + t: (key: string) => string; + layoutResetKey: number; +} + const MonitoringSection = ({ systemStats, containersData, t, layoutResetKey -}: { - systemStats: any; - containersData: any; - t: any; - layoutResetKey: number; -}) => { +}: MonitoringSectionProps) => {
🧹 Nitpick comments (3)
view/app/dashboard/components/system/load-average.tsx (1)
52-117: Consider removing unused components.The
LoadBar,LoadBarProps, andLoadBarSkeletoncomponents are defined but never used in the render path. They appear to be leftover from a previous implementation.Apply this diff to remove the unused code:
-interface LoadBarProps { - label: string; - value: number; -} - -const LoadBar: React.FC<LoadBarProps> = ({ label, value }) => ( - <div className="flex justify-between items-center"> - <TypographyMuted className="text-xs sm:text-sm">{label}</TypographyMuted> - <div className="flex items-center"> - <div className="w-20 sm:w-32 h-2 bg-gray-200 rounded-full mr-1 sm:mr-2"> - <div - className={`h-2 rounded-full ${value > 80 ? 'bg-destructive' : 'bg-primary'}`} - style={{ width: `${Math.min(value * 25, 100)}%` }} - /> - </div> - <TypographySmall className="text-xs sm:text-sm">{value.toFixed(2)}</TypographySmall> - </div> - </div> -); - export default LoadAverageCard; export function LoadAverageCardSkeleton() { const { t } = useTranslation(); return ( <Card className="overflow-hidden"> <CardHeader className="pb-2"> <CardTitle className="text-xs sm:text-sm font-medium flex items-center"> <Activity className="h-3 w-3 sm:h-4 sm:w-4 mr-1 sm:mr-2 text-muted-foreground" /> <TypographySmall>{t('dashboard.load.title')}</TypographySmall> </CardTitle> </CardHeader> <CardContent> <div className="space-y-4"> <div className="flex items-center justify-between"> <TypographyMuted>{t('dashboard.load.labels.oneMin')}</TypographyMuted> <Skeleton className="h-4 w-12" /> </div> <div className="flex items-center justify-between"> <TypographyMuted>{t('dashboard.load.labels.fiveMin')}</TypographyMuted> <Skeleton className="h-4 w-12" /> </div> <div className="flex items-center justify-between"> <TypographyMuted>{t('dashboard.load.labels.fifteenMin')}</TypographyMuted> <Skeleton className="h-4 w-12" /> </div> </div> </CardContent> </Card> ); } - -interface LoadBarSkeletonProps { - label: string; -} - -const LoadBarSkeleton: React.FC<LoadBarSkeletonProps> = ({ label }) => ( - <div className="flex justify-between items-center"> - <TypographyMuted className="text-xs sm:text-sm">{label}</TypographyMuted> - <div className="flex items-center"> - <Skeleton className="w-20 sm:w-32 h-2 rounded-full mr-1 sm:mr-2" /> - <Skeleton className="w-8 h-4" /> - </div> - </div> -);view/app/dashboard/components/system/system-stats.tsx (1)
25-40: Consider memoizing the items array for performance.The
systemStatsItemsarray is recreated on every render whensystemStatschanges. Since each item contains JSX (React elements), this could cause unnecessary re-renders of the DraggableGrid component.Apply this diff to memoize the items:
+import React, { useMemo } from 'react'; import { SystemStatsType } from '@/redux/types/monitor'; import SystemInfoCard, { SystemInfoCardSkeleton } from './system-info'; import LoadAverageCard, { LoadAverageCardSkeleton } from './load-average'; import MemoryUsageCard, { MemoryUsageCardSkeleton } from './memory-usage'; import { DraggableGrid, DraggableItem } from '@/components/ui/draggable-grid'; export interface SystemStatsProps { systemStats: SystemStatsType | null; } const SystemStats: React.FC<SystemStatsProps> = ({ systemStats }) => { if (!systemStats) { return ( <div className="space-y-4"> <SystemInfoCardSkeleton /> <LoadAverageCardSkeleton /> <MemoryUsageCardSkeleton /> </div> ); } - const systemStatsItems: DraggableItem[] = [ + const systemStatsItems = useMemo<DraggableItem[]>(() => [ { id: 'system-info', component: <SystemInfoCard systemStats={systemStats} /> }, { id: 'load-average', component: <LoadAverageCard systemStats={systemStats} /> }, { id: 'memory-usage', component: <MemoryUsageCard systemStats={systemStats} /> } - ]; + ], [systemStats]); return <DraggableGrid items={systemStatsItems} storageKey="system-stats-card-order" />; };view/app/dashboard/page.tsx (1)
154-192: Memoize dashboardItems to prevent unnecessary re-renders.The
dashboardItemsarray is recreated on every render with inline JSX components, which can cause the DraggableGrid to re-mount unnecessarily and lose drag state.Apply this diff to memoize the items:
+const dashboardItems: DraggableItem[] = React.useMemo(() => [ -const dashboardItems: DraggableItem[] = [ { id: 'system-info', component: <SystemInfoCard systemStats={systemStats} /> }, { id: 'load-average', component: <LoadAverageCard systemStats={systemStats} /> }, { id: 'memory-usage', component: <MemoryUsageCard systemStats={systemStats} /> }, { id: 'disk-usage', component: <DiskUsageCard systemStats={systemStats} /> }, { id: 'containers', component: ( <Card> <CardHeader className="flex flex-row items-center justify-between"> <CardTitle className="text-xs sm:text-sm font-bold flex items-center"> <Package className="h-3 w-3 sm:h-4 sm:w-4 mr-1 sm:mr-2 text-muted-foreground" /> <TypographySmall>{t('dashboard.containers.title')}</TypographySmall> </CardTitle> <Button variant="outline" size="sm" onClick={() => router.push('/containers')}> <ArrowRight className="h-3 w-3 sm:h-4 sm:w-4 mr-1 sm:mr-2 text-muted-foreground" /> {t('dashboard.containers.viewAll')} </Button> </CardHeader> <CardContent> <ContainersTable containersData={containersData} /> </CardContent> </Card> ), className: 'md:col-span-2' } -]; +], [systemStats, containersData, t, router]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
api/api/versions.json(1 hunks)view/app/dashboard/components/system/load-average.tsx(1 hunks)view/app/dashboard/components/system/memory-usage.tsx(1 hunks)view/app/dashboard/components/system/system-info.tsx(1 hunks)view/app/dashboard/components/system/system-stats.tsx(2 hunks)view/app/dashboard/page.tsx(5 hunks)view/components/ui/draggable-grid.tsx(1 hunks)view/package.json(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
view/app/dashboard/components/system/load-average.tsx (1)
view/redux/types/monitor.ts (1)
SystemStatsType(59-66)
view/app/dashboard/components/system/memory-usage.tsx (2)
view/redux/types/monitor.ts (1)
SystemStatsType(59-66)view/hooks/use-translation.ts (1)
useTranslation(18-74)
view/app/dashboard/components/system/system-info.tsx (1)
view/redux/types/monitor.ts (1)
SystemStatsType(59-66)
view/app/dashboard/components/system/system-stats.tsx (1)
view/components/ui/draggable-grid.tsx (2)
DraggableItem(26-30)DraggableGrid(93-235)
view/components/ui/draggable-grid.tsx (1)
view/lib/utils.ts (1)
cn(4-6)
view/app/dashboard/page.tsx (2)
view/app/dashboard/components/smtp-banner.tsx (1)
SMTPBanner(13-51)view/components/ui/draggable-grid.tsx (2)
DraggableItem(26-30)DraggableGrid(93-235)
🔇 Additional comments (9)
api/api/versions.json (1)
6-6: LGTM!The timestamp update reflects the current deployment and is consistent with standard versioning practices.
view/app/dashboard/components/system/load-average.tsx (1)
18-20: LGTM!The null-safety guard is correctly implemented and consistent with the skeleton rendering pattern used across other system cards.
view/app/dashboard/components/system/memory-usage.tsx (1)
20-22: LGTM!The null-safety guard is correctly implemented and follows the same pattern as the other system cards, ensuring consistent skeleton rendering behavior.
view/app/dashboard/components/system/system-info.tsx (1)
18-20: LGTM!The null-safety guard is correctly implemented and maintains consistency with the other system card components.
view/package.json (1)
15-17: LGTM!The @dnd-kit dependencies are correctly added to support the new drag-and-drop functionality. The versions are current and compatible with React 19.
view/components/ui/draggable-grid.tsx (1)
195-205: LGTM!The SSR-safe fallback rendering before mount prevents hydration mismatches while showing the initial layout correctly.
view/app/dashboard/page.tsx (3)
40-52: LGTM!The one-time drag hint with localStorage persistence provides good UX for onboarding users to the new drag-and-drop functionality.
54-57: LGTM!The reset layout mechanism correctly clears localStorage and triggers a grid refresh via the resetKey increment pattern.
79-111: LGTM!The Reset Layout button and drag hint banner provide clear user controls and guidance for the new draggable dashboard feature.
| useEffect(() => { | ||
| if (!mounted) return; | ||
| 
               | 
          ||
| if (typeof window !== 'undefined' && storageKey) { | ||
| const savedOrder = localStorage.getItem(storageKey); | ||
| if (savedOrder) { | ||
| try { | ||
| const orderIds = JSON.parse(savedOrder) as string[]; | ||
| const reordered = orderIds | ||
| .map(id => items.find(item => item.id === id)) | ||
| .filter(Boolean) as DraggableItem[]; | ||
| 
               | 
          ||
| const newItems = items.filter( | ||
| item => !orderIds.includes(item.id) | ||
| ); | ||
| 
               | 
          ||
| if (reordered.length > 0) { | ||
| setOrderedItems([...reordered, ...newItems]); | ||
| } else { | ||
| setOrderedItems(items); | ||
| } | ||
| } catch (e) { | ||
| console.error('Failed to load saved order:', e); | ||
| setOrderedItems(items); | ||
| } | ||
| } else { | ||
| // if no saved order, use default | ||
| setOrderedItems(items); | ||
| } | ||
| } | ||
| }, [mounted, storageKey, resetKey, items]); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix dependency array to prevent performance issues.
The items dependency in the array at line 152 will cause this effect to re-run every time the parent component re-renders with a new items array reference, even if the actual items haven't changed. This can lead to constant localStorage reloads and poor performance.
Consider using a stable dependency like items.map(i => i.id).join(',') or accept that items only sync on mount + resetKey changes:
   useEffect(() => {
     if (!mounted) return;
     if (typeof window !== 'undefined' && storageKey) {
       const savedOrder = localStorage.getItem(storageKey);
       if (savedOrder) {
         try {
           const orderIds = JSON.parse(savedOrder) as string[];
           const reordered = orderIds
             .map(id => items.find(item => item.id === id))
             .filter(Boolean) as DraggableItem[];
           const newItems = items.filter(
             item => !orderIds.includes(item.id)
           );
           if (reordered.length > 0) {
             setOrderedItems([...reordered, ...newItems]);
           } else {
             setOrderedItems(items);
           }
         } catch (e) {
           console.error('Failed to load saved order:', e);
           setOrderedItems(items);
         }
       } else {
         // if no saved order, use default
         setOrderedItems(items);
       }
     }
-  }, [mounted, storageKey, resetKey, items]);
+  }, [mounted, storageKey, resetKey]);Then rely on the second effect (lines 155-164) to handle external item changes. Alternatively, add a separate prop like itemsKey: string that changes only when items structurally change.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In view/components/ui/draggable-grid.tsx around lines 122 to 152 the useEffect
depends on the full items array which will retrigger on every parent re-render
when items has a new reference; replace the items dependency with a stable
identifier (for example items.map(i => i.id).join(',') or a dedicated itemsKey
prop that only changes when the item set actually changes) or remove items from
the dependency array and rely on mounted/resetKey to control syncing; update the
dependency array accordingly so the effect only runs on mount, resetKey changes,
or when the stable items identifier changes.
| useEffect(() => { | ||
| if (!mounted) return; | ||
| 
               | 
          ||
| if (orderedItems.length !== items.length) { | ||
| const savedOrder = localStorage.getItem(storageKey); | ||
| if (!savedOrder) { | ||
| setOrderedItems(items); | ||
| } | ||
| } | ||
| }, [items.length]); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add missing dependencies to effect.
The effect at lines 155-164 is missing storageKey and mounted in its dependency array, which could cause it to access stale values.
Apply this diff:
   // Update items when they change externally (but keep order if saved)
   useEffect(() => {
     if (!mounted) return;
     if (orderedItems.length !== items.length) {
       const savedOrder = localStorage.getItem(storageKey);
       if (!savedOrder) {
         setOrderedItems(items);
       }
     }
-  }, [items.length]);
+  }, [items.length, mounted, storageKey, orderedItems.length]);🤖 Prompt for AI Agents
In view/components/ui/draggable-grid.tsx around lines 155 to 164, the useEffect
currently depends only on items.length but also reads mounted,
orderedItems.length and storageKey; update the effect dependency array to
include mounted, storageKey and orderedItems.length (e.g. [items.length,
mounted, storageKey, orderedItems.length]) so the effect reruns when those
values change and avoids reading stale values; ensure no other external values
used inside the effect are left out of the dependency list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (4)
api/internal/features/dashboard/types.go (1)
45-56: Timestamp should be numeric epoch (frontend expects number).Current type is time.Time; the UI expects a number (Unix millis). This mismatch breaks parsing/serialization. Switch to int64 and populate with UnixMilli.
type SystemStats struct { OSType string `json:"os_type"` Hostname string `json:"hostname"` CPUInfo string `json:"cpu_info"` CPUCores int `json:"cpu_cores"` Memory MemoryStats `json:"memory"` Load LoadStats `json:"load"` Disk DiskStats `json:"disk"` KernelVersion string `json:"kernel_version"` Architecture string `json:"architecture"` - Timestamp time.Time `json:"timestamp"` + Timestamp int64 `json:"timestamp"` // Unix milliseconds }Follow-up: ensure all producers set Timestamp via time.Now().UnixMilli() (or sourceTime.UnixMilli()).
#!/bin/bash # Find SystemStats builders to update Timestamp assignment rg -n -C3 'SystemStats\\s*\\{' --glob '!**/vendor/**' rg -n 'Timestamp\\s*:' --glob '!**/vendor/**'view/app/dashboard/page.tsx (1)
126-136: Stop usinganyfor MonitoringSection props; add a proper props interface.Use real types for
systemStats,containersData, andt. Also includelayoutResetKeyin the interface. This mirrors prior feedback.+import type { SystemStatsType, ContainerData } from '@/redux/types/monitor'; +import type { useTranslation } from '@/hooks/use-translation'; @@ -const MonitoringSection = ({ - systemStats, - containersData, - t, - layoutResetKey -}: { - systemStats: any; - containersData: any; - t: any; - layoutResetKey: number; -}) => { +interface MonitoringSectionProps { + systemStats: SystemStatsType | null; + containersData: ContainerData[]; + t: ReturnType<typeof useTranslation>['t']; + layoutResetKey: number; +} + +const MonitoringSection = ({ + systemStats, + containersData, + t, + layoutResetKey +}: MonitoringSectionProps) => {view/components/ui/draggable-grid.tsx (2)
122-153: Avoid re-running the load-from-localStorage effect on every newitemsreference.Remove
itemsfrom deps or gate by a stable key; this was flagged earlier.- }, [mounted, storageKey, resetKey, items]); + }, [mounted, storageKey, resetKey]);Optionally add a prop
itemsKey(e.g.,items.map(i => i.id).join(',')) if you need to re-sync when the item set changes structurally.
155-165: Complete the dependency list for the second effect.Include
mounted,storageKey, andorderedItems.lengthto avoid stale reads.- }, [items.length]); + }, [items.length, mounted, storageKey, orderedItems.length]);
🧹 Nitpick comments (21)
api/internal/features/dashboard/system_stats.go (6)
35-41: Don't hard‑fail on OS type; degrade gracefully.If
uname -sfails, we currently abort the whole stats collection. Prefer continuing with partial stats and settingOSTypeto "unknown" (plus logging) to keep the dashboard responsive.- if err != nil { - m.BroadcastError(err.Error(), GetSystemStats) - return - } + if err != nil { + m.log.Log(logger.Error, "Failed to detect OS type", err.Error()) + osType = "unknown" + }
50-63: Avoid shelling out for host metadata when possible.Use gopsutil/runtime for hostname, kernel, and arch to reduce SSH calls and latency.
+ // Prefer library calls; fall back to command if needed + if hi, err := host.Info(); err == nil && hi.Hostname != "" { + stats.Hostname = strings.TrimSpace(hi.Hostname) + } else if hostname, err := m.getCommandOutput("hostname"); err == nil { + stats.Hostname = strings.TrimSpace(hostname) + } - if kernelVersion, err := m.getCommandOutput("uname -r"); err == nil { - stats.KernelVersion = strings.TrimSpace(kernelVersion) - } + if kv, err := host.KernelVersion(); err == nil && kv != "" { + stats.KernelVersion = strings.TrimSpace(kv) + } else if kernelVersion, err := m.getCommandOutput("uname -r"); err == nil { + stats.KernelVersion = strings.TrimSpace(kernelVersion) + } - if architecture, err := m.getCommandOutput("uname -m"); err == nil { - stats.Architecture = strings.TrimSpace(architecture) - } + if stats.Architecture == "" { + stats.Architecture = strings.TrimSpace(runtime.GOARCH) + }
65-69: Idiomatic uptime calculation.Safer and clearer as Duration math rather than uint64 nanoseconds conversion.
- uptime = time.Duration(hostInfo.Uptime * uint64(time.Second)).String() + uptime = (time.Duration(hostInfo.Uptime) * time.Second).String()
71-79: Prefer gopsutil for load averages; keep parsing as fallback.Avoid parsing
uptimeoutput; it varies by distro/shell. Useload.Avg()first, then fallback to parsing.+ // Try library first + if avg, err := load.Avg(); err == nil { + stats.Load.OneMin = avg.Load1 + stats.Load.FiveMin = avg.Load5 + stats.Load.FifteenMin = avg.Load15 + } else if loadAvg, err := m.getCommandOutput("uptime"); err == nil { - if loadAvg, err := m.getCommandOutput("uptime"); err == nil { loadAvgStr := strings.TrimSpace(loadAvg) stats.Load = parseLoadAverage(loadAvgStr) } // Set uptime after parsing load averages stats.Load.Uptime = uptimeAdd import:
+ "github.com/shirou/gopsutil/v3/load"
86-91: Redundant 0-check for CPU cores.
stats.CPUCoresis zero-initialized; just assign the count.- if stats.CPUCores == 0 { - if coreCount, err := cpu.Counts(true); err == nil { - stats.CPUCores = coreCount - } - } + if coreCount, err := cpu.Counts(true); err == nil { + stats.CPUCores = coreCount + }
158-178: Consider command timeouts to avoid hangs.
session.Run(cmd)can hang on network hiccups. If your SSH client connection isn't deadline-protected, add timeouts (connection-level or by usingsession.Start+Waitwith a timer) to bound execution.view/lib/i18n/locales/es.json (1)
808-822: System labels OK; fix memory unit mismatch and layout nesting.
- memory uses MB here while other locales use GB; standardize to GB to keep UI consistent.
 - Ensure
 layoutis a top-level key (not nested undernavigation) like in en/fr/ml.- "used": "{value} MB usado", - "total": "{value} MB total", + "used": "{value} GB usado", + "total": "{value} GB total",view/lib/i18n/locales/fr.json (1)
809-823: System labels look good; clean up unrelated strings.Elsewhere in this file, “Monitoreo” and “Partes de CPU” appear (Spanish). Replace with proper French (“Surveillance”, “Parts CPU”) to avoid mixed-language UI.
view/components/ui/bar-chart-component.tsx (1)
73-75: Prefer stable keys over index.Use a deterministic key (e.g., entry.name) to avoid unnecessary re-renders on data reordering.
- {data.map((entry, index) => ( - <Cell key={`cell-${index}`} fill={entry.fill} /> + {data.map((entry) => ( + <Cell key={entry.name} fill={entry.fill} /> ))}view/components/ui/doughnut-chart-component.tsx (2)
5-5: Make the chart responsive (avoid fixed 200×200).Use ResponsiveContainer so the chart adapts to its wrapper size.
-import { PieChart, Pie, Cell, Label } from 'recharts'; +import { PieChart, Pie, Cell, Label, ResponsiveContainer } from 'recharts'; @@ - <PieChart width={200} height={200}> + <ResponsiveContainer width="100%" height="100%"> + <PieChart> @@ - </PieChart> + </PieChart> + </ResponsiveContainer>Also applies to: 35-36, 79-80
47-49: Use stable keys for Cells.Prefer entry.name (assuming unique) over array index.
- {data.map((entry, index) => ( - <Cell key={`cell-${index}`} fill={entry.fill} /> + {data.map((entry) => ( + <Cell key={entry.name} fill={entry.fill} /> ))}view/app/dashboard/components/system/memory-usage.tsx (2)
28-29: Clamp free memory to non-negative.Minor guard against rounding or sampling inconsistencies.
- const freeMemory = memory.total - memory.used; + const freeMemory = Math.max(0, memory.total - memory.used);
31-42: Align chartConfig keys with data names; localize labels; prefer theme tokens over hex.
- chartConfig keys often drive tooltip/legend mapping; align them with data item names.
 - Localize “Used”, “Free”, “Total”.
 - Consider CSS tokens (e.g., hsl(var(--chart-1))) for dark/light theme consistency.
 - const chartData: DoughnutChartDataItem[] = [ + const chartData: DoughnutChartDataItem[] = [ { - name: 'Used', + name: t('dashboard.common.used'), value: memory.used, - fill: '#3b82f6' + fill: 'hsl(var(--chart-1))' }, { - name: 'Free', + name: t('dashboard.common.free'), value: freeMemory, - fill: '#10b981' + fill: 'hsl(var(--chart-2))' } ]; @@ - const chartConfig = { - used: { label: 'Used Memory', color: '#3b82f6' }, - free: { label: 'Free Memory', color: '#10b981' } - }; + const chartConfig = { + [t('dashboard.common.used')]: { label: t('dashboard.memory.usedLabel'), color: 'hsl(var(--chart-1))' }, + [t('dashboard.common.free')]: { label: t('dashboard.memory.freeLabel'), color: 'hsl(var(--chart-2))' } + }; @@ - centerLabel={{ - value: `${memory.percentage.toFixed(1)}%`, - subLabel: 'Used' - }} + centerLabel={{ + value: `${memory.percentage.toFixed(1)}%`, + subLabel: t('dashboard.common.used') + }} @@ - <TypographyMuted> - Used: {formatGB(memory.used)} GB + <TypographyMuted> + {t('dashboard.common.used')}: {formatGB(memory.used)} GB </TypographyMuted> @@ - <TypographyMuted> - Free: {formatGB(freeMemory)} GB + <TypographyMuted> + {t('dashboard.common.free')}: {formatGB(freeMemory)} GB </TypographyMuted> @@ - <TypographyMuted className="text-xs text-center"> - Total: {formatGB(memory.total)} GB + <TypographyMuted className="text-xs text-center"> + {t('dashboard.common.total')}: {formatGB(memory.total)} GB </TypographyMuted>Please also confirm the memory unit from the API is GiB; if it’s bytes, we should convert before formatting.
Also applies to: 44-53, 71-73, 85-94, 98-100
view/app/dashboard/components/system/load-average.tsx (1)
44-57: Align config keys with data names and localize labels; prefer theme tokens.
- Keep chartConfig keys consistent with displayed series names to ensure correct legend/tooltip mapping.
 - Localize axis and summary labels.
 - Use theme tokens for colors for better theming.
 - const chartConfig = { - oneMin: { label: '1 min', color: '#3b82f6' }, - fiveMin: { label: '5 min', color: '#10b981' }, - fifteenMin: { label: '15 min', color: '#f59e0b' } - }; + const chartConfig = { + '1 min': { label: t('dashboard.load.oneMin'), color: 'hsl(var(--chart-1))' }, + '5 min': { label: t('dashboard.load.fiveMin'), color: 'hsl(var(--chart-2))' }, + '15 min': { label: t('dashboard.load.fifteenMin'), color: 'hsl(var(--chart-3))' } + }; @@ - yAxisLabel="Load" - xAxisLabel="Time Period" + yAxisLabel={t('dashboard.load.axis.load')} + xAxisLabel={t('dashboard.load.axis.timePeriod')} @@ - <div className="h-2 w-2 rounded-full" style={{ backgroundColor: '#3b82f6' }} /> - <TypographyMuted className="text-xs">1 min</TypographyMuted> + <div className="h-2 w-2 rounded-full" style={{ backgroundColor: 'hsl(var(--chart-1))' }} /> + <TypographyMuted className="text-xs">{t('dashboard.load.oneMin')}</TypographyMuted> @@ - <div className="h-2 w-2 rounded-full" style={{ backgroundColor: '#10b981' }} /> - <TypographyMuted className="text-xs">5 min</TypographyMuted> + <div className="h-2 w-2 rounded-full" style={{ backgroundColor: 'hsl(var(--chart-2))' }} /> + <TypographyMuted className="text-xs">{t('dashboard.load.fiveMin')}</TypographyMuted> @@ - <div className="h-2 w-2 rounded-full" style={{ backgroundColor: '#f59e0b' }} /> - <TypographyMuted className="text-xs">15 min</TypographyMuted> + <div className="h-2 w-2 rounded-full" style={{ backgroundColor: 'hsl(var(--chart-3))' }} /> + <TypographyMuted className="text-xs">{t('dashboard.load.fifteenMin')}</TypographyMuted>If ChartTooltipContent relies on config keys to format labels, this alignment prevents mismatches.
Also applies to: 74-76, 81-101, 128-140
view/app/dashboard/page.tsx (1)
154-193: MemoizedashboardItemsto avoid unstableitemsprop causing extra work.Recreate the array only when inputs change; this cooperates with
DraggableGrideffects and prevents unnecessary localStorage reads.- const dashboardItems: DraggableItem[] = [ + const dashboardItems = React.useMemo<DraggableItem[]>(() => [ { id: 'system-info', component: <SystemInfoCard systemStats={systemStats} />, className: 'md:col-span-2' }, { id: 'load-average', component: <LoadAverageCard systemStats={systemStats} /> }, { id: 'memory-usage', component: <MemoryUsageCard systemStats={systemStats} /> }, { id: 'disk-usage', component: <DiskUsageCard systemStats={systemStats} /> }, { id: 'containers', component: ( <Card> @@ </Card> ), className: 'md:col-span-2' } - ]; + ], [systemStats, containersData, t]);view/app/dashboard/components/system/system-info.tsx (3)
22-26: Localize literals and units; add safer fallbacks.Replace hardcoded “N/A”, “GB”, and “cores” with translations; ensure uptime falls back when absent.
- const memoryDisplay = `${memory.used.toFixed(1)} / ${memory.total.toFixed(1)} GB (${memory.percentage.toFixed(1)}%)`; + const { t } = useTranslation(); // already present above + const NA = t('common.na'); + const GB = t('units.gb'); // add to locales if missing + const memoryDisplay = + `${memory.used.toFixed(1)} / ${memory.total.toFixed(1)} ${GB} (${memory.percentage.toFixed(1)}%)`; @@ - value: os_type || 'N/A' + value: os_type || NA @@ - value: hostname || 'N/A' + value: hostname || NA @@ - value: cpu_info || 'N/A' + value: cpu_info || NA @@ - value: cpu_cores > 0 ? `${cpu_cores} cores` : 'N/A' + value: cpu_cores > 0 ? t('dashboard.system.values.cpuCores', { count: String(cpu_cores) }) : NA @@ - value: kernel_version || 'N/A' + value: kernel_version || NA @@ - value: load.uptime?.replaceAll(/([hms])(\d)/g, '$1 $2') + value: load?.uptime + ? load.uptime.replaceAll(/([hms])(\d)/g, '$1 $2') + : NA @@ - value: architecture || 'N/A' + value: architecture || NAAdd these new keys if needed:
- common.na
 - units.gb
 - dashboard.system.values.cpuCores = "{count} cores" (or localized equivalent)
 Also applies to: 29-67
3-5: Remove unused imports.
Cpuisn’t used; drop it to reduce bundle size.-import { Server, Cpu, HardDrive, Activity, Terminal, Box, CpuIcon, ScreenShare, ServerCog } from 'lucide-react'; +import { Server, HardDrive, Activity, Terminal, Box, CpuIcon, ScreenShare, ServerCog } from 'lucide-react';
80-83: Prefer a stable key over array index.Use label (string) as key to avoid potential reconciliation issues.
- {systemInfoItems.map((item, index) => ( - <div key={index} className="flex items-start gap-3 p-2 rounded-lg hover:bg-muted/50 transition-colors"> + {systemInfoItems.map((item) => ( + <div key={String(item.label)} className="flex items-start gap-3 p-2 rounded-lg hover:bg-muted/50 transition-colors">view/components/ui/draggable-grid.tsx (3)
41-47: Remove unusedisDraggingprop fromSortableItem.It’s never used; rely on
useSortable().isDragging.-function SortableItem({ - item, - isDragging -}: { - item: DraggableItem; - isDragging?: boolean; -}) { +function SortableItem({ item }: { item: DraggableItem }) {
73-82: Improve drag handle accessibility and focus affordance.Expose the handle on keyboard focus and add an accessible label.
- <div + <div {...listeners} {...attributes} - className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-3 opacity-0 group-hover:opacity-100 transition-opacity cursor-grab active:cursor-grabbing z-10 touch-none" - title="Drag to reorder" + className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-3 opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity cursor-grab active:cursor-grabbing z-10 touch-none" + aria-label="Drag to reorder" + title="Drag to reorder" >If you prefer full semantics, render a
<button type="button">for the handle and spreadattributes/listenersonto it.
155-165: Keep order but refresh item content when props change with same length.When
items.lengthis unchanged butcomponent/classNameupdates,orderedItemsstays stale. Merge by id to refresh content while preserving order.- // Update items when they change externally (but keep order if saved) - useEffect(() => { + // Update items when they change externally (refresh content but keep saved order) + useEffect(() => { if (!mounted) return; - if (orderedItems.length !== items.length) { + if (orderedItems.length !== items.length) { const savedOrder = localStorage.getItem(storageKey); if (!savedOrder) { setOrderedItems(items); } + } else { + const incomingById = new Map(items.map(i => [i.id, i])); + setOrderedItems(prev => + prev.map(i => incomingById.get(i.id) ?? i) + ); } - }, [items.length]); + }, [items.length, mounted, storageKey, orderedItems.length, items]);Pair this with memoized
itemsfrom the parent (seepage.tsx) to avoid unnecessary churn.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
api/api/versions.json(1 hunks)api/internal/features/dashboard/system_stats.go(1 hunks)api/internal/features/dashboard/types.go(1 hunks)view/app/dashboard/components/system/disk-usage.tsx(2 hunks)view/app/dashboard/components/system/load-average.tsx(1 hunks)view/app/dashboard/components/system/memory-usage.tsx(2 hunks)view/app/dashboard/components/system/system-info.tsx(2 hunks)view/app/dashboard/page.tsx(5 hunks)view/components/ui/bar-chart-component.tsx(1 hunks)view/components/ui/doughnut-chart-component.tsx(1 hunks)view/components/ui/draggable-grid.tsx(1 hunks)view/lib/i18n/locales/en.json(1 hunks)view/lib/i18n/locales/es.json(1 hunks)view/lib/i18n/locales/fr.json(1 hunks)view/lib/i18n/locales/kn.json(1 hunks)view/lib/i18n/locales/ml.json(1 hunks)view/redux/types/monitor.ts(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- view/app/dashboard/components/system/disk-usage.tsx
 
🧰 Additional context used
🧬 Code graph analysis (8)
view/app/dashboard/components/system/memory-usage.tsx (5)
view/redux/types/monitor.ts (1)
SystemStatsType(59-70)view/hooks/use-translation.ts (1)
useTranslation(18-74)view/components/ui/doughnut-chart-component.tsx (2)
DoughnutChartDataItem(7-11)DoughnutChartComponent(25-82)view/components/ui/typography.tsx (2)
TypographySmall(99-112)TypographyMuted(114-122)view/components/ui/skeleton.tsx (1)
Skeleton(13-13)
view/app/dashboard/components/system/load-average.tsx (3)
view/redux/types/monitor.ts (1)
SystemStatsType(59-70)view/components/ui/bar-chart-component.tsx (2)
BarChartDataItem(7-11)BarChartComponent(22-80)view/components/ui/typography.tsx (2)
TypographySmall(99-112)TypographyMuted(114-122)
api/internal/features/dashboard/types.go (1)
view/redux/types/monitor.ts (3)
MemoryStats(27-32)LoadStats(34-39)DiskStats(50-57)
view/redux/types/monitor.ts (1)
api/internal/features/dashboard/types.go (3)
MemoryStats(58-63)LoadStats(65-70)DiskStats(81-88)
view/app/dashboard/page.tsx (1)
view/components/ui/draggable-grid.tsx (2)
DraggableItem(26-30)DraggableGrid(93-235)
view/components/ui/bar-chart-component.tsx (1)
view/components/ui/chart.tsx (3)
ChartContainer(316-316)ChartTooltip(317-317)ChartTooltipContent(318-318)
view/components/ui/draggable-grid.tsx (1)
view/lib/utils.ts (1)
cn(4-6)
view/app/dashboard/components/system/system-info.tsx (3)
view/redux/types/monitor.ts (1)
SystemStatsType(59-70)view/hooks/use-translation.ts (1)
useTranslation(18-74)view/components/ui/typography.tsx (2)
TypographySmall(99-112)TypographyMuted(114-122)
🔇 Additional comments (7)
api/api/versions.json (1)
6-6: LGTM, just confirm release timestamp source.Looks fine. Ensure this release_date is generated from your build/release process and is monotonic across releases to avoid update-check regressions.
view/lib/i18n/locales/ml.json (1)
824-838: System labels added correctly.Keys align with other locales. No issues spotted here.
view/lib/i18n/locales/kn.json (1)
805-819: System labels addition looks good.Matches the expected schema.
view/app/dashboard/components/system/memory-usage.tsx (1)
21-23: Null-state handling LGTM.Early return to skeleton is clean and prevents crashes.
view/app/dashboard/components/system/load-average.tsx (1)
19-21: Null-state handling LGTM.Early skeleton return is correct and consistent with other cards.
view/components/ui/bar-chart-component.tsx (1)
35-37: No issues found—ChartContainer correctly implements ResponsiveContainer.The verification confirms that ChartContainer properly wraps children with
RechartsPrimitive.ResponsiveContainer(line 62 of view/components/ui/chart.tsx), ensuring the chart will resize predictably. The BarChart component will handle responsiveness as expected.view/app/dashboard/components/system/system-info.tsx (1)
24-26: No action needed—memory units are correct.The backend converts memory from bytes to GB before sending (
float64(memInfo.Total) / bytesInGBwherebytesInGB = 1024 * 1024 * 1024), so the frontend values are already in GB. The code at line 25 is correct.Likely an incorrect or invalid review comment.
        
          
                view/app/dashboard/page.tsx
              
                Outdated
          
        
      | <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={handleResetLayout} | ||
| className="shrink-0" | ||
| > | ||
| <RefreshCw className="mr-2 h-4 w-4" /> | ||
| Reset Layout | ||
| </Button> | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Externalize hardcoded UI text to i18n.
“Reset Layout”, “Customize Your Dashboard”, the hint paragraph, and “Got it” should use t(...) like the rest of the page.
-          <Button
+          <Button
             variant="outline"
             size="sm"
             onClick={handleResetLayout}
             className="shrink-0"
+            aria-label={t('dashboard.actions.resetLayout')}
           >
             <RefreshCw className="mr-2 h-4 w-4" />
-            Reset Layout
+            {t('dashboard.actions.resetLayout')}
           </Button>
@@
-                <p className="text-sm font-medium text-foreground">Customize Your Dashboard</p>
+                <p className="text-sm font-medium text-foreground">
+                  {t('dashboard.dragHint.title')}
+                </p>
                 <p className="text-xs text-muted-foreground mt-1">
-                  Hover over any card to see the drag handle on the left. Click and drag to rearrange cards in your preferred order. Your layout will be saved automatically.
+                  {t('dashboard.dragHint.body')}
                 </p>
@@
-            >
-              Got it
+            >
+              {t('common.gotIt')}
             </Button>Ensure keys exist (or add them) in locales:
- dashboard.actions.resetLayout
 - dashboard.dragHint.title
 - dashboard.dragHint.body
 - common.gotIt
 
Also applies to: 90-111
| "lastUpdated": "Last Updated", | ||
| "hostname": "Hostname", | ||
| "kernelVersion": "Kernel Version", | ||
| "architecture": "Architecture", | ||
| "memory": "Memory", | ||
| "labels": { | ||
| "operatingSystem": "Operating System", | ||
| "hostname": "Hostname", | ||
| "cpu": "CPU", | ||
| "cpuCores": "CPU Cores", | ||
| "memory": "Memory", | ||
| "kernelVersion": "Kernel Version", | ||
| "uptime": "Uptime", | ||
| "architecture": "Architecture" | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
New system labels OK; run an i18n sanity check.
Keys look correct. To prevent runtime issues, please verify placeholders and structure across locales (e.g., non-English placeholder names, misplaced 'layout', unit inconsistencies).
🏁 Script executed:
#!/bin/bash
# i18n sanity checks: placeholder tokens, layout position, memory units consistency.
python - << 'PY'
import json, glob, re, sys, os
files = sorted(glob.glob("view/lib/i18n/locales/*.json"))
allowed_placeholders = {"name","count","number","current","total"}
issues = []
# 1) Placeholder tokens should be ASCII and from allowed set
def walk(obj, path=""):
    if isinstance(obj, dict):
        for k,v in obj.items():
            walk(v, f"{path}.{k}" if path else k)
    elif isinstance(obj, list):
        for i,v in enumerate(obj):
            walk(v, f"{path}[{i}]")
    elif isinstance(obj, str):
        for m in re.finditer(r"\{([^}]+)\}", obj):
            token = m.group(1)
            if not token.isascii():
                issues.append(("non_ascii_placeholder", path, token, obj))
            elif token not in allowed_placeholders:
                issues.append(("unknown_placeholder", path, token, obj))
# 2) layout must be top-level, not nested inside navigation
layout_issues = []
# 3) Memory unit consistency
mem_units = {}
def unit_from(s):
    # capture GB/MB/Go/Mo tokens
    m = re.search(r"\b(GO|Go|GB|MB|Mo)\b", s)
    return m.group(0) if m else None
for f in files:
    with open(f, "r", encoding="utf-8") as fh:
        data = json.load(fh)
    walk(data)
    root_has_layout = "layout" in data
    nav_has_layout = isinstance(data.get("navigation"), dict) and "layout" in data["navigation"]
    if not root_has_layout or nav_has_layout:
        layout_issues.append((os.path.basename(f), root_has_layout, nav_has_layout))
    # memory units
    try:
        used = data["dashboard"]["memory"]["used"]
        total = data["dashboard"]["memory"]["total"]
        mem_units[os.path.basename(f)] = (unit_from(used), unit_from(total))
    except Exception:
        pass
if issues:
    print("Placeholder issues:")
    for t,p,token,val in issues:
        print(f" - {t}: {p} -> '{token}' in '{val}'")
    print()
if layout_issues:
    print("Layout key issues (expect layout at root, not under navigation):")
    for name,root_ok,nav_has in layout_issues:
        print(f" - {name}: root_layout={root_ok}, navigation.layout={nav_has}")
    print()
# Unit consistency report
if mem_units:
    print("Memory units by locale (used,total):")
    for k,v in mem_units.items():
        print(f" - {k}: {v}")
    units = set(u for pair in mem_units.values() for u in pair if u)
    if len(units) > 1:
        print(f"\nInconsistent units detected across locales: {sorted(units)}")
        print("Recommend standardizing to GB equivalents for dashboard.memory.*")
else:
    print("No memory keys found.")
PYLength of output: 9256
Fix critical i18n issues in locale files before merge.
Verification revealed three breaking issues:
- 
Non-ASCII placeholders in Malayalam (ml.json): Strings use localized placeholder names ({എണ്ണുക}, {പേര്}, {മൂല്യം}) instead of ASCII equivalents. Runtime interpolation will fail—replace with ASCII tokens (e.g., {count}, {name}, {value}).
 - 
Misplaced layout key in Spanish (es.json): The
layoutkey is nested undernavigationinstead of at root level, causing config/parsing mismatches. - 
Inconsistent memory units: Spanish uses MB; English, Kannada, and Malayalam use GB; French uses Go. Standardize to GB across all locales to prevent user confusion and data interpretation errors.
 
🤖 Prompt for AI Agents
In view/lib/i18n/locales/en.json around lines 825 to 839, the "memory"
label/unit must be standardized to "GB" (not "MB" or "Go") and ensure the nested
labels keys are consistent with other locales; update the "memory" value to use
"GB" and keep the label keys unchanged. Also, fix the other reported locale
issues: in view/lib/i18n/locales/ml.json replace any non-ASCII placeholder names
(e.g., {എണ്ണുക}, {പേര്}, {മൂല്യം}) with ASCII tokens like {count}, {name},
{value}; and in view/lib/i18n/locales/es.json move the misplaced "layout" key
out from under "navigation" to the root of the JSON so parsing/config matches
expected structure.
| export interface SystemStatsType { | ||
| os_type: string; | ||
| hostname: string; | ||
| cpu_info: string; | ||
| cpu_cores: number; | ||
| memory: MemoryStats; | ||
| load: LoadStats; | ||
| disk: DiskStats; | ||
| kernel_version: string; | ||
| architecture: string; | ||
| timestamp: number; | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Critical: timestamp type mismatches backend payload.
Backend sets Timestamp: time.Now() (RFC3339 string). Here timestamp is number. This will break parsing.
Choose one:
- Preferred: change FE to 
timestamp: string. - Or change BE to send epoch millis (int64) and keep FE as number.
 
FE fix:
 export interface SystemStatsType {
   os_type: string;
   hostname: string;
   cpu_info: string;
   cpu_cores: number;
   memory: MemoryStats;
   load: LoadStats;
   disk: DiskStats;
   kernel_version: string;
   architecture: string;
-  timestamp: number;
+  timestamp: string;
 }BE alternative (Go) for consistency if you prefer numbers:
// types.go
type SystemStats struct {
    // ...
-   Timestamp time.Time `json:"timestamp"`
+   Timestamp int64 `json:"timestamp"` // epoch millis
}
// system_stats.go
stats := SystemStats{
    // ...
-   Timestamp: time.Now(),
+   Timestamp: time.Now().UnixMilli(),
}I'll verify the timestamp type mismatch claim by examining the backend and frontend code.
🏁 Script executed:
#!/bin/bash
# Find backend Go files with SystemStats struct
find . -type f -name "*.go" | head -20Length of output: 960
🏁 Script executed:
#!/bin/bash
# Search for SystemStats struct definition in Go files
rg -n "type SystemStats struct" --type goLength of output: 69
🏁 Script executed:
#!/bin/bash
# Search for Timestamp field definitions in Go structs
rg -n "Timestamp.*time\.Time" --type go -A2 -B2Length of output: 817
🏁 Script executed:
#!/bin/bash
# Search for any custom JSON marshaling related to timestamps
rg -n "MarshalJSON|UnmarshalJSON" --type go -A5Length of output: 0
🏁 Script executed:
#!/bin/bash
# Find where SystemStats is being returned/serialized in handlers
rg -n "SystemStats\{" --type go -A10Length of output: 867
🏁 Script executed:
#!/bin/bash
# Check the FE monitor.ts file for how timestamp is used
rg -n "timestamp" view/redux/types/monitor.ts -B2 -A2Length of output: 84
🏁 Script executed:
#!/bin/bash
# Search for timestamp parsing/conversion in FE code
rg -n "\.timestamp" --type ts --type tsx -C3Length of output: 27
🏁 Script executed:
#!/bin/bash
# Find the endpoint handler that returns SystemStats
rg -n "func.*SystemStats" --type go -A15Length of output: 1199
🏁 Script executed:
#!/bin/bash
# Search for dashboard controller/handler files
find . -type f -name "*dashboard*" -name "*.go" | grep -E "(controller|handler|route)"Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look for any middleware or wrapper that transforms the response
rg -n "json\.Marshal|json\.Unmarshal|ResponseWriter" api/internal/features/dashboard/ --type go -B2 -A2Length of output: 754
Critical: timestamp type mismatch breaks payload parsing at runtime.
Backend Go struct defines Timestamp time.Time (api/internal/features/dashboard/types.go:55) serialized via standard json.Marshal(), which converts time.Time to RFC3339 strings (e.g., "2025-10-25T14:30:00Z"). Frontend expects timestamp: number, causing parse failure.
Choose one fix:
- Preferred: Change FE to 
timestamp: string - Alternative: Change BE to epoch millis (int64)
 
FE fix:
 export interface SystemStatsType {
   os_type: string;
   hostname: string;
   cpu_info: string;
   cpu_cores: number;
   memory: MemoryStats;
   load: LoadStats;
   disk: DiskStats;
   kernel_version: string;
   architecture: string;
-  timestamp: number;
+  timestamp: string;
 }BE alternative (api/internal/features/dashboard/types.go):
type SystemStats struct {
    // ...
-   Timestamp time.Time `json:"timestamp"`
+   Timestamp int64 `json:"timestamp"` // epoch millis
}Then update api/internal/features/dashboard/system_stats.go:44:
-   Timestamp: time.Now(),
+   Timestamp: time.Now().UnixMilli(),🤖 Prompt for AI Agents
In view/redux/types/monitor.ts around lines 59 to 70, the frontend
SystemStatsType declares timestamp as a number but the backend sends an RFC3339
string; update the FE type to timestamp: string to match the serialized
time.Time from the backend, and search/update any places that consume or parse
SystemStatsType.timestamp (parsing to Date or number where needed) so they
handle a string RFC3339 timestamp correctly.
| 
           @coderabbitai: Please update the title, summary, and description  | 
    
| 
          
 Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain@zhravan: I've updated the PR title and description to accurately reflect the changes in this pull request. The new title is: "feat: enhance dashboard with draggable layout, charts, and extended system metrics" The description now includes: 
 The description provides a comprehensive view of what this PR accomplishes, making it much easier for reviewers to understand the scope and impact of the changes. 🏁 Script executed: Length of output: 71 @zhravan: ✅ Successfully updated PR #536! The PR now has: 
 You can view the updated PR at: #536  | 
    
| <TypographyMuted>{t('dashboard.load.labels.fifteenMin')}</TypographyMuted> | ||
| <TypographySmall>{load.fifteenMin.toFixed(2)}</TypographySmall> | ||
| 
               | 
          ||
| {/* Summary Stats with Color Indicators */} | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be refactored and reused as a component instead of repeating
| <TypographyMuted>{t('dashboard.load.labels.fifteenMin')}</TypographyMuted> | ||
| <Skeleton className="h-4 w-12" /> | ||
| <div className="grid grid-cols-3 gap-2 text-center"> | ||
| <div> | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, can be refactored and reused
        
          
                view/app/dashboard/page.tsx
              
                Outdated
          
        
      | }); | ||
| const { isFeatureEnabled, isLoading: isFeatureFlagsLoading } = useFeatureFlags(); | ||
| const [showDragHint, setShowDragHint] = React.useState(false); | ||
| const [mounted, setMounted] = React.useState(false); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move out of the page to hook for all these logics,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (5)
view/redux/types/monitor.ts (1)
69-81: Timestamp type mismatch with backend payload (blocks runtime).FE declares timestamp: number but BE sends RFC3339 string (time.Time). Align both sides: either change FE to string or make BE send Unix millis (int64). Repeating prior finding to keep this visible.
[suggested FE fix]
export interface SystemStatsType { os_type: string; hostname: string; cpu_info: string; cpu_cores: number; cpu: CPUStats; memory: MemoryStats; load: LoadStats; disk: DiskStats; kernel_version: string; architecture: string; - timestamp: number; + timestamp: string; }—or keep number and update BE (see comment in api/internal/features/dashboard/types.go).
Run to locate uses expecting number:
#!/bin/bash rg -n --type ts --type tsx -C2 '\btimestamp\b' view | sed -n '1,200p'api/internal/features/dashboard/types.go (1)
45-57: Standardize timestamp to Unix millis to match FE.Current: time.Time → JSON string. FE expects number. Prefer sending int64 millis.
[suggested BE change]
type SystemStats struct { OSType string `json:"os_type"` Hostname string `json:"hostname"` CPUInfo string `json:"cpu_info"` CPUCores int `json:"cpu_cores"` CPU CPUStats `json:"cpu"` Memory MemoryStats `json:"memory"` Load LoadStats `json:"load"` Disk DiskStats `json:"disk"` KernelVersion string `json:"kernel_version"` Architecture string `json:"architecture"` - Timestamp time.Time `json:"timestamp"` + Timestamp int64 `json:"timestamp"` // Unix millis }And set with time.Now().UnixMilli() where populated.
Find constructions to update:
#!/bin/bash rg -n 'SystemStats\{|\bTimestamp:\s*' api/internal --type go -C3view/app/dashboard/page.tsx (3)
80-88: Externalize “Reset Layout” and add aria-label.Use i18n and improve a11y.
- <Button + <Button variant="outline" size="sm" onClick={handleResetLayout} className="shrink-0" + aria-label={t('dashboard.actions.resetLayout')} > <RefreshCw className="mr-2 h-4 w-4" /> - Reset Layout + {t('dashboard.actions.resetLayout')} </Button>
90-111: Externalize drag-hint banner strings.Replace hardcoded title/body and button text.
- <p className="text-sm font-medium text-foreground">Customize Your Dashboard</p> + <p className="text-sm font-medium text-foreground"> + {t('dashboard.dragHint.title')} + </p> <p className="text-xs text-muted-foreground mt-1"> - Hover over any card to see the drag handle on the left. Click and drag to rearrange cards in your preferred order. Your layout will be saved automatically. + {t('dashboard.dragHint.body')} </p> @@ - Got it + {t('common.gotIt')}
127-137: Replaceanyprops with concrete types.Define a props interface and reuse existing domain types.
-import { useRouter } from 'next/navigation'; +import { useRouter } from 'next/navigation'; +import { SystemStatsType, ContainerData } from '@/redux/types/monitor'; @@ -const MonitoringSection = ({ +interface MonitoringSectionProps { + systemStats: SystemStatsType | null; + containersData: ContainerData[]; + t: (key: string, opts?: any) => string; + layoutResetKey: number; +} + +const MonitoringSection = ({ systemStats, containersData, t, layoutResetKey -}: { - systemStats: any; - containersData: any; - t: any; - layoutResetKey: number; -}) => { +}: MonitoringSectionProps) => {Run to confirm no lingering
anyin this file and to findContainerDataif path differs:#!/bin/bash rg -nP --type=ts -C2 '\bMonitoringSection\s*\(' view/app/dashboard/page.tsx fd -t f -e ts -e tsx 'monitor' | xargs rg -nP 'interface\s+ContainerData|type\s+ContainerData'
🧹 Nitpick comments (19)
view/app/dashboard/components/system/system-metric-card.tsx (1)
25-36: Minor a11y: hide decorative icon and signal loading.
- Mark the icon decorative to avoid SR noise.
 - Expose loading via aria-busy.
 - <CardTitle className="text-xs sm:text-sm font-medium flex items-center"> - <Icon className="h-3 w-3 sm:h-4 sm:w-4 mr-1 sm:mr-2 text-muted-foreground" /> + <CardTitle className="text-xs sm:text-sm font-medium flex items-center"> + <Icon aria-hidden="true" className="h-3 w-3 sm:h-4 sm:w-4 mr-1 sm:mr-2 text-muted-foreground" /> <TypographySmall>{title}</TypographySmall> </CardTitle> </CardHeader> - <CardContent className="flex-1"> + <CardContent className="flex-1" aria-busy={isLoading || undefined}> {content} </CardContent>view/lib/i18n/locales/kn.json (1)
788-794: CPU and system labels are correct; fix 15 pre-existing typos in notifications section.The additions at lines 788-794 and 812-826 are properly formatted with no issues. The file contains 15 pre-existing instances of "ಅ th" (corrupted text) in the notifications section (lines 465-563) that should be corrected for translation quality:
Lines 465, 466, 469, 476, 477, 486, 487, 493, 513, 517, 526, 542, 559, 560, 563.
view/app/dashboard/hooks/use-system-metric.ts (1)
11-15: Widenttyping to support i18n paramsYour
useTranslation()exposest(key, params?). The narrowed(key: string) => stringblocks param usage at call sites.-interface UseSystemMetricResult<T> { - data: T; - isLoading: boolean; - t: (key: string) => string; -} +interface UseSystemMetricResult<T> { + data: T; + isLoading: boolean; + t: (key: string, params?: Record<string, string>) => string; +}view/app/dashboard/components/system/cpu-usage.tsx (3)
45-47: Clamp overall usage to [0, 100]Avoid odd values from backend rounding.
- {formatPercentage(cpu?.overall || 0)}% + {formatPercentage(Math.min(100, Math.max(0, cpu?.overall ?? 0)))}%
65-76: Hoist color palette out of the mapMinor perf/readability nit: avoid re-allocating
colorsfor each core.- {topCores.length > 0 && ( + {topCores.length > 0 && ( <div className="grid grid-cols-3 gap-2 text-center"> - {topCores.map((core) => { - const colors = [ + {(() => { + const colors = [ CHART_COLORS.blue, CHART_COLORS.green, CHART_COLORS.orange, CHART_COLORS.purple, CHART_COLORS.red, CHART_COLORS.yellow, - ]; - const color = colors[core.core_id % colors.length]; - - return ( + ]; + return topCores.map((core) => { + const color = colors[core.core_id % colors.length]; + return ( <div key={core.core_id} className="flex flex-col items-center gap-1"> <div className="flex items-center gap-1"> <div className="h-2 w-2 rounded-full" style={{ backgroundColor: color }} /> <TypographyMuted className="text-xs">Core {core.core_id}</TypographyMuted> </div> <TypographySmall className="text-sm font-bold"> {formatPercentage(core.usage)}% </TypographySmall> </div> - ); - })} + ); + }); + })()} </div> )}
111-116: Localize “Overall” in skeletonKeep skeletons translatable.
-function CPUUsageCardSkeletonContent() { - return ( +function CPUUsageCardSkeletonContent() { + const { t } = useSystemMetric({ + systemStats: null, + extractData: (s) => s.cpu, + defaultData: DEFAULT_METRICS.cpu, + }); + return ( <div className="space-y-4"> <div className="text-center"> - <TypographyMuted className="text-xs">Overall</TypographyMuted> + <TypographyMuted className="text-xs">{t('dashboard.cpu.overall')}</TypographyMuted>view/app/dashboard/components/system/constants.ts (1)
27-33: Avoidany[]fordisk.allMountsType this to match
DiskUsageexpectations.- allMounts: [] as any[] + allMounts: [] as Array<{ mountPoint: string; size: string; used: string; capacity: string }>view/app/dashboard/components/system/disk-usage.tsx (2)
41-46: Clamp progress widthGuard against out-of-range percentages.
- <div - className={`h-2 rounded-full bg-primary`} - style={{ width: `${disk.percentage}%` }} - /> + <div + className="h-2 rounded-full bg-primary" + style={{ width: `${Math.min(100, Math.max(0, disk.percentage))}%` }} + aria-valuenow={Math.min(100, Math.max(0, disk.percentage))} + aria-valuemin={0} + aria-valuemax={100} + role="progressbar" + />
48-56: Use i18n params instead of manual string replaceThe
t()function supports parameterized replacement via optional second argument:t(key, { value })instead oft(key).replace('{value}', ...). This is cleaner and matches the i18n hook implementation.<TypographyMuted className="text-xs truncate max-w-[80px] sm:max-w-[100px]"> - {t('dashboard.disk.used').replace('{value}', disk.used.toFixed(2))} + {t('dashboard.disk.used', { value: disk.used.toFixed(2) })} </TypographyMuted> <TypographyMuted className="text-xs truncate max-w-[60px] sm:max-w-[80px]"> - {t('dashboard.disk.percentage').replace('{value}', formatPercentage(disk.percentage))} + {t('dashboard.disk.percentage', { value: formatPercentage(disk.percentage) })} </TypographyMuted> <TypographyMuted className="text-xs truncate max-w-[80px] sm:max-w-[100px]"> - {t('dashboard.disk.total').replace('{value}', disk.total.toFixed(2))} + {t('dashboard.disk.total', { value: disk.total.toFixed(2) })} </TypographyMuted>Note: Similar patterns exist in other files (EditUserDialog.tsx, log-viewer.tsx, header.tsx, deploymentStatusChart.tsx) and should be normalized across the codebase.
view/app/dashboard/components/system/system-stats.tsx (1)
27-46: Memoize systemStatsItems to stabilize references and prevent unnecessary reorder effect runs
systemStatsItemsis recreated each render. DraggableGrid's useEffect depends on theitemsprop (line 122 in draggable-grid.tsx), so a new reference triggers effect reruns and repeated localStorage reorder application. Memoize withuseMemoandsystemStatsdependency to prevent this.Optionally add
gridColsprop for responsive layout (consistent with dashboard usage).view/lib/i18n/locales/en.json (1)
808-814: CPU labels added — looks good. Add complementary keys used by charts/tooltips.To fully support the new chart UIs and remove hardcoded strings, please add:
- dashboard.memory.free
 - dashboard.load.axis.x, dashboard.load.axis.y
 - dashboard.actions.resetLayout
 - dashboard.dragHint.title, dashboard.dragHint.body
 - common.gotIt
 Example patch:
"dashboard": { + "actions": { + "resetLayout": "Reset Layout" + }, "load": { "title": "Load Average", + "axis": { "x": "Time Period", "y": "Load" }, "labels": { "oneMin": "1 minute", "fiveMin": "5 minutes", "fifteenMin": "15 minutes" } }, "cpu": { "title": "CPU Usage (per core)", "overall": "Overall", "usage": "Usage (%)", "cores": "Cores", "perCore": "Per Core" }, "memory": { "title": "Memory Usage", "used": "{value} GB used", + "free": "{value} GB free", "total": "{value} GB total", "percentage": "{value}%" }, + "dragHint": { + "title": "Customize Your Dashboard", + "body": "Hover over any card to see the drag handle on the left. Click and drag to rearrange cards. Your layout will be saved automatically." + },And at root:
"common": { "readLess": "Read less", + "gotIt": "Got it" },view/app/dashboard/components/system/memory-usage.tsx (1)
41-47: Externalize memory labels and units to i18n.Replace hardcoded "Used/Free/Total" and center label with locale strings (keys added in en.json comment).
- centerLabel={{ - value: `${memory.percentage.toFixed(1)}%`, - subLabel: 'Used' - }} + centerLabel={{ + value: `${memory.percentage.toFixed(1)}%`, + subLabel: t('dashboard.memory.used').replace('{value}', '') + }} @@ - <TypographyMuted> - Used: {formatGB(memory.used)} GB - </TypographyMuted> + <TypographyMuted> + {t('dashboard.memory.used', { value: formatGB(memory.used) })} + </TypographyMuted> @@ - <TypographyMuted> - Free: {formatGB(freeMemory)} GB - </TypographyMuted> + <TypographyMuted> + {t('dashboard.memory.free', { value: formatGB(freeMemory) })} + </TypographyMuted> @@ - <TypographyMuted className="text-xs text-center"> - Total: {formatGB(memory.total)} GB - </TypographyMuted> + <TypographyMuted className="text-xs text-center"> + {t('dashboard.memory.total', { value: formatGB(memory.total) })} + </TypographyMuted>Also applies to: 56-68, 71-74
view/app/dashboard/components/system/load-average.tsx (3)
35-35: Remove spacer
s; use CSS utility spacing instead.- <br /><br /><br />
41-45: Externalize axis and period labels.Use i18n keys so tooltips/text are localized.
- yAxisLabel="Load" - xAxisLabel="Time Period" + yAxisLabel={t('dashboard.load.axis.y')} + xAxisLabel={t('dashboard.load.axis.x')} @@ - <TypographyMuted className="text-xs">1 min</TypographyMuted> + <TypographyMuted className="text-xs"> + {t('dashboard.load.labels.oneMin')} + </TypographyMuted> @@ - <TypographyMuted className="text-xs">5 min</TypographyMuted> + <TypographyMuted className="text-xs"> + {t('dashboard.load.labels.fiveMin')} + </TypographyMuted> @@ - <TypographyMuted className="text-xs">15 min</TypographyMuted> + <TypographyMuted className="text-xs"> + {t('dashboard.load.labels.fifteenMin')} + </TypographyMuted>Also applies to: 49-70
25-27: Optional: feed localized names into chart data for tooltip parity.Consider passing labels into createLoadAverageChartData so bar names match localized text. See utils.ts comment for a backward‑compatible signature change.
view/app/dashboard/components/system/utils.ts (4)
10-30: Allow localized labels in load-average helpers (keep English defaults).Make functions accept optional labels to support i18n tooltips without breaking callers.
-export const createLoadAverageChartData = (load: { +export const createLoadAverageChartData = (load: { oneMin: number; fiveMin: number; fifteenMin: number; -}): BarChartDataItem[] => [ +}, labels?: { oneMin?: string; fiveMin?: string; fifteenMin?: string }): BarChartDataItem[] => [ { - name: '1 min', + name: labels?.oneMin ?? '1 min', value: load.oneMin, fill: CHART_COLORS.blue, }, { - name: '5 min', + name: labels?.fiveMin ?? '5 min', value: load.fiveMin, fill: CHART_COLORS.green, }, { - name: '15 min', + name: labels?.fifteenMin ?? '15 min', value: load.fifteenMin, fill: CHART_COLORS.orange, }, ]; -export const createLoadAverageChartConfig = () => ({ +export const createLoadAverageChartConfig = (labels?: { oneMin?: string; fiveMin?: string; fifteenMin?: string }) => ({ oneMin: { - label: '1 min', + label: labels?.oneMin ?? '1 min', color: CHART_COLORS.blue, }, fiveMin: { - label: '5 min', + label: labels?.fiveMin ?? '5 min', color: CHART_COLORS.green, }, fifteenMin: { - label: '15 min', + label: labels?.fifteenMin ?? '15 min', color: CHART_COLORS.orange, }, });Also applies to: 32-45
47-72: Same for memory helpers: inject labels to localize chart/tooltip.-export const createMemoryChartData = ( - used: number, - free: number -): DoughnutChartDataItem[] => [ +export const createMemoryChartData = ( + used: number, + free: number, + labels?: { used?: string; free?: string } +): DoughnutChartDataItem[] => [ { - name: 'Used', + name: labels?.used ?? 'Used', value: used, fill: CHART_COLORS.blue, }, { - name: 'Free', + name: labels?.free ?? 'Free', value: free, fill: CHART_COLORS.green, }, ]; -export const createMemoryChartConfig = () => ({ +export const createMemoryChartConfig = (labels?: { used?: string; free?: string }) => ({ used: { - label: 'Used Memory', + label: labels?.used ?? 'Used Memory', color: CHART_COLORS.blue, }, free: { - label: 'Free Memory', + label: labels?.free ?? 'Free Memory', color: CHART_COLORS.green, }, });
75-97: CPU helpers: expose a label formatter for per‑core names.Support localization or custom naming while keeping defaults.
-export const createCPUChartData = ( - perCore: Array<{ core_id: number; usage: number }> -): BarChartDataItem[] => { +export const createCPUChartData = ( + perCore: Array<{ core_id: number; usage: number }>, + labelForCore?: (coreId: number) => string +): BarChartDataItem[] => { @@ - return perCore.map((core) => ({ - name: `Core ${core.core_id}`, + return perCore.map((core) => ({ + name: labelForCore ? labelForCore(core.core_id) : `Core ${core.core_id}`, value: core.usage, fill: colors[core.core_id % colors.length], })); }; -export const createCPUChartConfig = (coreCount: number) => { +export const createCPUChartConfig = (coreCount: number, labelForCore?: (coreIndex: number) => string) => { @@ for (let i = 0; i < coreCount; i++) { config[`core${i}`] = { - label: `Core ${i}`, + label: labelForCore ? labelForCore(i) : `Core ${i}`, color: colors[i % colors.length], }; }Also applies to: 99-118
6-7: Minor: guard formatters against NaN.-export const formatGB = (value: number): string => `${value.toFixed(2)}`; -export const formatPercentage = (value: number): string => `${value.toFixed(1)}`; +export const formatGB = (value: number): string => + Number.isFinite(value) ? value.toFixed(2) : '0.00'; +export const formatPercentage = (value: number): string => + Number.isFinite(value) ? value.toFixed(1) : '0.0';
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
view/yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (19)
api/api/versions.json(1 hunks)api/internal/features/dashboard/system_stats.go(2 hunks)api/internal/features/dashboard/types.go(1 hunks)view/app/dashboard/components/system/constants.ts(1 hunks)view/app/dashboard/components/system/cpu-usage.tsx(1 hunks)view/app/dashboard/components/system/disk-usage.tsx(3 hunks)view/app/dashboard/components/system/load-average.tsx(1 hunks)view/app/dashboard/components/system/memory-usage.tsx(1 hunks)view/app/dashboard/components/system/system-metric-card.tsx(1 hunks)view/app/dashboard/components/system/system-stats.tsx(2 hunks)view/app/dashboard/components/system/utils.ts(1 hunks)view/app/dashboard/hooks/use-system-metric.ts(1 hunks)view/app/dashboard/page.tsx(5 hunks)view/lib/i18n/locales/en.json(2 hunks)view/lib/i18n/locales/es.json(2 hunks)view/lib/i18n/locales/fr.json(2 hunks)view/lib/i18n/locales/kn.json(2 hunks)view/lib/i18n/locales/ml.json(2 hunks)view/redux/types/monitor.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- view/lib/i18n/locales/es.json
 - api/internal/features/dashboard/system_stats.go
 - api/api/versions.json
 
🧰 Additional context used
🧬 Code graph analysis (11)
view/app/dashboard/hooks/use-system-metric.ts (2)
view/redux/types/monitor.ts (1)
SystemStatsType(69-81)view/hooks/use-translation.ts (1)
useTranslation(18-74)
view/app/dashboard/components/system/cpu-usage.tsx (7)
view/redux/types/monitor.ts (1)
SystemStatsType(69-81)view/app/dashboard/hooks/use-system-metric.ts (1)
useSystemMetric(21-41)view/app/dashboard/components/system/constants.ts (2)
DEFAULT_METRICS(12-33)CHART_COLORS(2-9)view/app/dashboard/components/system/utils.ts (3)
createCPUChartData(75-97)createCPUChartConfig(99-118)formatPercentage(7-7)view/app/dashboard/components/system/system-metric-card.tsx (1)
SystemMetricCard(16-38)view/components/ui/typography.tsx (2)
TypographyMuted(114-122)TypographySmall(99-112)view/components/ui/bar-chart-component.tsx (1)
BarChartComponent(22-80)
view/app/dashboard/components/system/system-stats.tsx (2)
view/app/dashboard/components/system/cpu-usage.tsx (1)
CPUUsageCardSkeleton(129-146)view/components/ui/draggable-grid.tsx (2)
DraggableItem(26-30)DraggableGrid(93-235)
view/app/dashboard/components/system/system-metric-card.tsx (1)
view/components/ui/typography.tsx (1)
TypographySmall(99-112)
view/app/dashboard/components/system/memory-usage.tsx (8)
view/redux/types/monitor.ts (1)
SystemStatsType(69-81)view/app/dashboard/hooks/use-system-metric.ts (1)
useSystemMetric(21-41)view/app/dashboard/components/system/constants.ts (2)
DEFAULT_METRICS(12-33)CHART_COLORS(2-9)view/app/dashboard/components/system/utils.ts (3)
createMemoryChartData(47-61)createMemoryChartConfig(63-72)formatGB(6-6)view/app/dashboard/components/system/system-metric-card.tsx (1)
SystemMetricCard(16-38)view/components/ui/doughnut-chart-component.tsx (1)
DoughnutChartComponent(25-82)view/components/ui/typography.tsx (1)
TypographyMuted(114-122)view/components/ui/skeleton.tsx (1)
Skeleton(13-13)
view/app/dashboard/components/system/utils.ts (3)
view/components/ui/bar-chart-component.tsx (1)
BarChartDataItem(7-11)view/app/dashboard/components/system/constants.ts (1)
CHART_COLORS(2-9)view/components/ui/doughnut-chart-component.tsx (1)
DoughnutChartDataItem(7-11)
view/redux/types/monitor.ts (1)
api/internal/features/dashboard/types.go (5)
CPUCore(59-62)CPUStats(64-67)MemoryStats(69-74)LoadStats(76-81)DiskStats(92-99)
view/app/dashboard/page.tsx (2)
view/app/dashboard/components/smtp-banner.tsx (1)
SMTPBanner(13-51)view/components/ui/draggable-grid.tsx (2)
DraggableItem(26-30)DraggableGrid(93-235)
view/app/dashboard/components/system/load-average.tsx (7)
view/redux/types/monitor.ts (1)
SystemStatsType(69-81)view/app/dashboard/hooks/use-system-metric.ts (1)
useSystemMetric(21-41)view/app/dashboard/components/system/constants.ts (2)
DEFAULT_METRICS(12-33)CHART_COLORS(2-9)view/app/dashboard/components/system/utils.ts (2)
createLoadAverageChartData(10-30)createLoadAverageChartConfig(32-45)view/app/dashboard/components/system/system-metric-card.tsx (1)
SystemMetricCard(16-38)view/components/ui/bar-chart-component.tsx (1)
BarChartComponent(22-80)view/components/ui/typography.tsx (2)
TypographyMuted(114-122)TypographySmall(99-112)
view/app/dashboard/components/system/disk-usage.tsx (6)
view/app/dashboard/hooks/use-system-metric.ts (1)
useSystemMetric(21-41)view/app/dashboard/components/system/constants.ts (1)
DEFAULT_METRICS(12-33)view/app/dashboard/components/system/system-metric-card.tsx (1)
SystemMetricCard(16-38)view/components/ui/typography.tsx (2)
TypographyMuted(114-122)TypographySmall(99-112)view/app/dashboard/components/system/utils.ts (1)
formatPercentage(7-7)view/hooks/use-translation.ts (1)
useTranslation(18-74)
api/internal/features/dashboard/types.go (1)
view/redux/types/monitor.ts (5)
CPUStats(64-67)MemoryStats(27-32)LoadStats(34-39)DiskStats(50-57)CPUCore(59-62)
🔇 Additional comments (4)
view/lib/i18n/locales/fr.json (1)
792-798: Keys align; verify parity with base locale.CPU and system label additions look correct and keep placeholder keys intact. Please verify this set matches en.json to avoid missing labels at runtime.
Also applies to: 816-830
view/lib/i18n/locales/ml.json (1)
807-813: Incorrect line numbers in review. The specified sections do not contain the problematic placeholders.Lines 807-813 (CPU section) and 831-845 (system labels) contain only localized text with no interpolation placeholders—neither have the non-ASCII placeholder issue.
However, the broader concern is valid: non-ASCII placeholders like {എണ്ണുക}, {മൂല്യം}, {പേര്}, {നമ്പർ}, {നിലവിലുള്ളത്}, and {നിലവിലെ}/{ആകെ} exist throughout the file (lines 68, 689–690, 781, 787–789, 816–818, 862, 914–915, 948, 1109–1110, 1140, 1319) and must be normalized to canonical English keys for i18next interpolation to work.
Likely an incorrect or invalid review comment.
view/app/dashboard/components/system/system-stats.tsx (1)
17-23: Skeleton path extension looks goodCPU skeleton aligns with other metric placeholders and keeps layout stable while loading.
view/lib/i18n/locales/en.json (1)
832-846: System label map is consistent. Ensure all locales mirror this structure.Please mirror dashboard.system.labels across es/fr/kn/ml to avoid runtime misses.
| const perCoreData = cpu?.per_core || []; | ||
| const chartData = createCPUChartData(perCoreData); | ||
| const chartConfig = createCPUChartConfig(perCoreData.length); | ||
| 
               | 
          
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Align chartConfig with the Bar’s dataKey
Bar uses dataKey="value", but createCPUChartConfig returns keys like core0, core1. Tooltips/legend may not resolve.
-  const chartConfig = createCPUChartConfig(perCoreData.length);
+  const chartConfig = {
+    value: { label: t('dashboard.cpu.usage'), color: CHART_COLORS.blue }
+  };Confirm what ChartContainer/ChartTooltipContent expect:
🏁 Script executed:
#!/bin/bash
rg -n -C3 "export (const|function) ChartContainer|ChartTooltipContent" --type=ts --type=tsxLength of output: 27
---
🏁 Script executed:
```shell
#!/bin/bash
# Search for ChartContainer and ChartTooltipContent exports without file type restriction
rg -n -C3 "export (const|function) (ChartContainer|ChartTooltipContent)" -g "*.ts" -g "*.tsx"
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search for Bar component usage with dataKey in the cpu-usage file
rg -n "dataKey" view/app/dashboard/components/system/cpu-usage.tsx -A2 -B2Length of output: 0
🏁 Script executed:
#!/bin/bash
# Find createCPUChartConfig function definition
rg -n "createCPUChartConfig" --type=ts -A10Length of output: 2777
---
🏁 Script executed:
```shell
#!/bin/bash
# Get complete createCPUChartConfig function definition
rg -n "export const createCPUChartConfig" -A25 view/app/dashboard/components/system/utils.ts
Length of output: 544
🏁 Script executed:
#!/bin/bash
# Get the complete cpu-usage.tsx file to see Bar component and how chartConfig is used
cat view/app/dashboard/components/system/cpu-usage.tsxLength of output: 4877
🏁 Script executed:
#!/bin/bash
# Check createCPUChartData function as well
rg -n "export const createCPUChartData" -A20 view/app/dashboard/components/system/utils.tsLength of output: 609
---
🏁 Script executed:
```shell
#!/bin/bash
# Find BarChartComponent definition
fd -t f "bar-chart-component" -e ts -e tsx
Length of output: 42
🏁 Script executed:
#!/bin/bash
# Get BarChartComponent implementation
rg -n "export.*BarChartComponent" -A30 -g "*.ts" -g "*.tsx"Length of output: 2462
---
🏁 Script executed:
```shell
#!/bin/bash
# Get full BarChartComponent file
cat view/components/ui/bar-chart-component.tsx
Length of output: 2417
🏁 Script executed:
#!/bin/bash
# Check the skeleton hardcoded "Overall" string - not translated
rg -n "Overall" view/app/dashboard/components/system/cpu-usage.tsxLength of output: 111
Fix chartConfig keys and add missing i18n translations
The chartConfig keys (core0, core1, etc.) don't align with the Bar's dataKey="value". ChartTooltipContent expects config keys matching the data field names. Since the Bar reads from dataKey="value", the config should be keyed by "value":
-  const chartConfig = createCPUChartConfig(perCoreData.length);
+  const chartConfig = {
+    value: { label: t('dashboard.cpu.usage'), color: CHART_COLORS.blue }
+  };Also, the skeleton has a hardcoded untranslated string at line 111:
-        <TypographyMuted className="text-xs">Overall</TypographyMuted>
+        <TypographyMuted className="text-xs">{t('dashboard.cpu.overall')}</TypographyMuted>As a secondary improvement, the colors array is duplicated in three places (utils.ts lines 101–108 and 83–90, cpu-usage.tsx lines 67–73). Consider moving it to constants for maintainability.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const perCoreData = cpu?.per_core || []; | |
| const chartData = createCPUChartData(perCoreData); | |
| const chartConfig = createCPUChartConfig(perCoreData.length); | |
| const perCoreData = cpu?.per_core || []; | |
| const chartData = createCPUChartData(perCoreData); | |
| const chartConfig = { | |
| value: { label: t('dashboard.cpu.usage'), color: CHART_COLORS.blue } | |
| }; | 
🤖 Prompt for AI Agents
In view/app/dashboard/components/system/cpu-usage.tsx around lines 25 to 28,
change the chartConfig so its keys align with the Bar's dataKey ("value")
instead of per-core keys (core0, core1, ...): produce a config keyed by "value"
(and preserve any per-core color mapping elsewhere if needed) so
ChartTooltipContent can look up labels/formatters by the same field name the Bar
uses; also fix the untranslated hardcoded string at line 111 by replacing it
with the appropriate i18n key/translation call; finally, deduplicate the colors
array by moving the shared color definitions into a single exported constant
(e.g., a colors constant in utils.ts or a new constants file) and import it from
cpu-usage.tsx and the two utils.ts locations (lines ~83–90 and ~101–108) so all
three sites use the same source of truth.
| // Calculate free memory | ||
| const freeMemory = memory.total - memory.used; | ||
| 
               | 
          
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clamp free memory to avoid negative slices.
If used > total (transient/reporting skew), free becomes negative and the chart may misrender. Clamp to zero.
-  const freeMemory = memory.total - memory.used;
+  const freeMemory = Math.max(0, memory.total - memory.used);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Calculate free memory | |
| const freeMemory = memory.total - memory.used; | |
| // Calculate free memory | |
| const freeMemory = Math.max(0, memory.total - memory.used); | |
🤖 Prompt for AI Agents
In view/app/dashboard/components/system/memory-usage.tsx around lines 25 to 27,
the freeMemory calculation can become negative when memory.used > memory.total
causing chart misrendering; change the calculation to clamp the result to zero
(e.g., set freeMemory to the max of 0 and memory.total - memory.used) so slices
never receive negative values and ensure the variable remains a number for
downstream rendering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
view/app/dashboard/page.tsx (3)
60-68: Externalize hardcoded UI text to i18n.The "Reset Layout" button text should use
t(...)for consistency with the rest of the page and to support internationalization.Apply this diff:
<Button variant="outline" size="sm" onClick={handleResetLayout} className="shrink-0" + aria-label={t('dashboard.actions.resetLayout')} > <RefreshCw className="mr-2 h-4 w-4" /> - Reset Layout + {t('dashboard.actions.resetLayout')} </Button>Ensure the key
dashboard.actions.resetLayoutexists in locale files.
70-92: Externalize hardcoded drag hint text to i18n.The drag hint banner contains hardcoded English strings: "Customize Your Dashboard", the hint paragraph, and "Got it". These should use
t(...)for internationalization support.Apply this diff:
{mounted && showDragHint && ( <div className="mb-4 p-4 bg-primary/5 border border-primary/20 rounded-lg flex items-start justify-between gap-4"> <div className="flex items-start gap-3"> <div className="mt-0.5 text-primary"> <Info className="h-5 w-5" /> </div> <div className="flex-1"> - <p className="text-sm font-medium text-foreground">Customize Your Dashboard</p> + <p className="text-sm font-medium text-foreground"> + {t('dashboard.dragHint.title')} + </p> <p className="text-xs text-muted-foreground mt-1"> - Hover over any card to see the drag handle on the left. Click and drag to rearrange cards in your preferred order. Your layout will be saved automatically. + {t('dashboard.dragHint.body')} </p> </div> </div> <Button variant="ghost" size="default" onClick={dismissHint} className="shrink-0" > - Got it + {t('common.gotIt')} </Button> </div> )}Ensure these keys exist in locale files:
dashboard.dragHint.titledashboard.dragHint.bodycommon.gotIt
107-117: Replaceanytypes with proper type definitions.The
MonitoringSectioncomponent usesanytypes for its props, which defeats the purpose of TypeScript's type safety and increases the risk of runtime errors.Apply this diff:
+import { SystemStatsType } from '@/redux/types/monitor'; +import type { ContainerData } from '@/redux/types/monitor'; +import { translationKey } from '@/hooks/use-translation'; + +interface MonitoringSectionProps { + systemStats: SystemStatsType | null; + containersData: ContainerData[]; + t: (key: translationKey, params?: Record<string, string>) => string; + layoutResetKey: number; +} + const MonitoringSection = ({ systemStats, containersData, t, layoutResetKey -}: { - systemStats: any; - containersData: any; - t: any; - layoutResetKey: number; -}) => { +}: MonitoringSectionProps) => {
🧹 Nitpick comments (3)
view/app/dashboard/hooks/use-dashboard.ts (2)
32-40: Consider extracting localStorage keys as constants.The localStorage keys
'dashboard-drag-hint-seen'and'dashboard-card-order'are hardcoded strings. Extracting them as named constants would improve maintainability and reduce typo risks.Apply this diff:
+'use client'; + +import React from 'react'; +import { useFeatureFlags } from '@/hooks/features_provider'; +import { useAppSelector } from '@/redux/hooks'; +import { useGetSMTPConfigurationsQuery } from '@/redux/services/settings/notificationApi'; +import { FeatureNames } from '@/types/feature-flags'; +import useMonitor from './use-monitor'; + +const STORAGE_KEYS = { + DRAG_HINT_SEEN: 'dashboard-drag-hint-seen', + CARD_ORDER: 'dashboard-card-order', +} as const; + export function useDashboard() { const { isFeatureEnabled, isLoading: isFeatureFlagsLoading } = useFeatureFlags(); const { containersData, systemStats } = useMonitor(); const activeOrganization = useAppSelector((state) => state.user.activeOrganization); const { data: smtpConfig } = useGetSMTPConfigurationsQuery(activeOrganization?.id, { skip: !activeOrganization }); const [showDragHint, setShowDragHint] = React.useState(false); const [mounted, setMounted] = React.useState(false); const [layoutResetKey, setLayoutResetKey] = React.useState(0); React.useEffect(() => { setMounted(true); - const hasSeenHint = localStorage.getItem('dashboard-drag-hint-seen'); + const hasSeenHint = localStorage.getItem(STORAGE_KEYS.DRAG_HINT_SEEN); if (!hasSeenHint) { setShowDragHint(true); } }, []); const dismissHint = React.useCallback(() => { setShowDragHint(false); - localStorage.setItem('dashboard-drag-hint-seen', 'true'); + localStorage.setItem(STORAGE_KEYS.DRAG_HINT_SEEN, 'true'); }, []); const handleResetLayout = React.useCallback(() => { - localStorage.removeItem('dashboard-card-order'); + localStorage.removeItem(STORAGE_KEYS.CARD_ORDER); setLayoutResetKey((prev) => prev + 1); }, []);
23-30: The 'use client' directive mitigates SSR risks, but localStorage access could be more explicit.The
'use client'directive at the top of the file ensures this hook runs only on the client. The rendering inpage.tsxis correctly guarded with{mounted && showDragHint &&}to prevent hydration mismatch. However, the pattern of accessinglocalStoragedirectly without an explicit client-side check is repeated across dashboard components (smtp-banner.tsxuses the same pattern). While this works with the current setup, best practice would be to add a guard:React.useEffect(() => { setMounted(true); if (typeof window !== 'undefined') { const hasSeenHint = localStorage.getItem('dashboard-drag-hint-seen'); if (!hasSeenHint) { setShowDragHint(true); } } }, []);This makes the client-side requirement explicit and is more resilient if the code is refactored elsewhere.
view/app/dashboard/hooks/use-system-metric.ts (1)
5-9: Document stability requirements for function and object parameters.The
extractDatafunction anddefaultDataobject/array are included in theuseMemodependencies (line 34), but the hook doesn't enforce or document that these must be stable references. If callers pass inline functions or newly created objects on every render, memoization will be ineffective.Add JSDoc to the interface to guide consumers:
interface UseSystemMetricOptions<T> { + /** System stats object from the monitor hook */ systemStats: SystemStatsType | null; + /** + * Data extraction function. Must be stable (e.g., useCallback or module-level). + * Called only when systemStats changes. + */ extractData: (stats: SystemStatsType) => T; + /** + * Default value returned when systemStats is null. Must be stable + * (e.g., useMemo, constant, or module-level) to avoid unnecessary re-renders. + */ defaultData: T; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
view/app/dashboard/hooks/use-dashboard.ts(1 hunks)view/app/dashboard/hooks/use-system-metric.ts(1 hunks)view/app/dashboard/page.tsx(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
view/app/dashboard/hooks/use-system-metric.ts (2)
view/redux/types/monitor.ts (1)
SystemStatsType(69-81)view/hooks/use-translation.ts (2)
translationKey(16-16)useTranslation(18-74)
view/app/dashboard/page.tsx (3)
view/app/dashboard/hooks/use-dashboard.ts (1)
useDashboard(10-65)view/app/dashboard/components/smtp-banner.tsx (1)
SMTPBanner(13-51)view/components/ui/draggable-grid.tsx (2)
DraggableItem(26-30)DraggableGrid(93-235)
view/app/dashboard/hooks/use-dashboard.ts (2)
view/hooks/features_provider.tsx (1)
useFeatureFlags(59-59)view/redux/hooks.ts (1)
useAppSelector(5-5)
🔇 Additional comments (6)
view/app/dashboard/hooks/use-dashboard.ts (1)
46-64: LGTM! Clean return structure with organized exports.The hook provides a well-organized API surface grouped by concern (feature flags, data, UI state, actions), making it easy for consumers to understand and use.
view/app/dashboard/hooks/use-system-metric.ts (1)
21-41: LGTM! Clean generic hook with proper memoization.The hook correctly:
- Derives loading state from the presence of
 systemStats- Memoizes extracted data with appropriate dependencies
 - Returns a consistent interface including translations
 Assuming callers provide stable references for
extractDataanddefaultData(as suggested above), this implementation is solid.view/app/dashboard/page.tsx (4)
27-38: LGTM! Clean integration of the new useDashboard hook.The destructuring clearly shows all the data, state, and actions needed by the page. This improves readability and addresses the past comment requesting logic be moved out of the page to a hook.
120-133: LGTM! Skeleton loading state provides good UX.The skeleton implementation correctly handles the loading state before
systemStatsis available, matching the expected grid layout.
136-179: Well-structured draggable items array.The
dashboardItemsarray clearly defines each dashboard block with appropriate IDs, components, and grid span classes. The inline containers card definition keeps related logic together.
181-188: LGTM! Clean DraggableGrid integration.The
DraggableGridusage is straightforward with:
- Descriptive
 storageKeyfor persistence- Responsive grid columns
 resetKeyprop to trigger layout resetThis provides a good drag-and-drop UX with automatic persistence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
♻️ Duplicate comments (3)
view/app/dashboard/page.tsx (2)
98-107: i18n + a11y for Reset Layout button.- <Button + <Button variant="outline" size="sm" onClick={onResetLayout} className="shrink-0" + aria-label={t('dashboard.actions.resetLayout')} > <RefreshCw className="mr-2 h-4 w-4" /> - Reset Layout + {t('dashboard.actions.resetLayout')} </Button>Ensure dashboard.actions.resetLayout exists in locales. Based on learnings.
130-144: i18n the drag hint content.- <p className="text-sm font-medium text-foreground">Customize Your Dashboard</p> + <p className="text-sm font-medium text-foreground"> + {t('dashboard.dragHint.title')} + </p> <p className="text-xs text-muted-foreground mt-1"> - Hover over any card to see the drag handle on the left. Click and drag to rearrange cards in your preferred order. Your layout will be saved automatically. + {t('dashboard.dragHint.body')} </p> @@ - > - Got it + > + {t('common.gotIt')} </Button>Add keys if missing:
- dashboard.dragHint.title, dashboard.dragHint.body, common.gotIt. Based on learnings.
 view/app/dashboard/components/system/memory-usage.tsx (1)
25-27: Clamp free memory to avoid negative slices.- const freeMemory = memory.total - memory.used; + const freeMemory = Math.max(0, memory.total - memory.used);
🧹 Nitpick comments (12)
view/app/dashboard/components/system/skeletons/memory-usage.tsx (1)
4-4: Icon mismatch: BarChart used for memory doughnut visualization.The PR description mentions "Memory Usage card: doughnut chart," but Line 4 imports
BarChartand Line 45 uses it as the icon. Consider using a more semantically appropriate icon likePieChartor a memory-related icon (e.g.,HardDrive,Database) to match the doughnut chart visualization.Apply this diff:
-import { BarChart } from 'lucide-react'; +import { PieChart } from 'lucide-react';icon={BarChart} + icon={PieChart}Also applies to: 45-45
view/app/dashboard/components/system/skeletons/cpu-usage.tsx (1)
22-22: Consider making skeleton core count configurable or representative.The skeleton hardcodes 3 cores (
[0, 1, 2]), which may not match typical systems (commonly 4, 8, or 16+ cores). Consider using a representative count (e.g., 4 or 6) or making it configurable.Example adjustment for 4 cores:
- {[0, 1, 2].map((i) => ( + {[0, 1, 2, 3].map((i) => (view/app/dashboard/components/utils/constants.ts (1)
14-16: Optional: Remove redundantas numbertype annotations.TypeScript infers numeric literals (e.g.,
0) asnumberby default. The explicitas numberannotations on Lines 14-16, 19, 23-25, and 28-30 are redundant and can be simplified for cleaner code.Example simplification:
load: { - oneMin: 0 as number, - fiveMin: 0 as number, - fifteenMin: 0 as number + oneMin: 0, + fiveMin: 0, + fifteenMin: 0 },Also applies to: 19-19, 23-25, 28-30
view/app/dashboard/components/system/skeletons/disk-usage.tsx (2)
17-19: Progress bar placeholder shows empty.The progress bar skeleton (Lines 17-19) has no width style on the inner div, so it appears empty. Consider adding a representative width (e.g.,
style={{ width: '60%' }}) to better preview the actual UI.<div className="w-full h-2 bg-gray-200 rounded-full"> - <div className="h-2 rounded-full bg-gray-400" /> + <div className="h-2 rounded-full bg-gray-400" style={{ width: '60%' }} /> </div>
51-66: Consider showing multiple skeleton rows.The table shows only one skeleton row (Lines 52-65). Showing 2-3 rows would better represent a typical disk usage table with multiple mount points.
<tbody className="text-xxs sm:text-xs"> - <tr> - <td className="text-left pr-1 sm:pr-2"> - <Skeleton className="h-3 w-10" /> - </td> - <td className="text-right pr-1 sm:pr-2"> - <Skeleton className="h-3 w-10" /> - </td> - <td className="text-right pr-1 sm:pr-2"> - <Skeleton className="h-3 w-10" /> - </td> - <td className="text-right"> - <Skeleton className="h-3 w-10" /> - </td> - </tr> + {[0, 1, 2].map((i) => ( + <tr key={i}> + <td className="text-left pr-1 sm:pr-2"> + <Skeleton className="h-3 w-10" /> + </td> + <td className="text-right pr-1 sm:pr-2"> + <Skeleton className="h-3 w-10" /> + </td> + <td className="text-right pr-1 sm:pr-2"> + <Skeleton className="h-3 w-10" /> + </td> + <td className="text-right"> + <Skeleton className="h-3 w-10" /> + </td> + </tr> + ))} </tbody>view/app/dashboard/components/system/disk-usage.tsx (1)
49-56: Use translation params instead of manual string replace.Leverage t(key, { value }) to support languages that don’t place the value last.
- {t('dashboard.disk.used').replace('{value}', disk.used.toFixed(2))} + {t('dashboard.disk.used', { value: disk.used.toFixed(2) })} @@ - {t('dashboard.disk.percentage').replace('{value}', formatPercentage(disk.percentage))} + {t('dashboard.disk.percentage', { value: formatPercentage(disk.percentage) })} @@ - {t('dashboard.disk.total').replace('{value}', disk.total.toFixed(2))} + {t('dashboard.disk.total', { value: disk.total.toFixed(2) })}Ensure these keys accept a "value" param in locales. Based on learnings.
view/app/dashboard/components/system/system-info.tsx (3)
29-47: i18n: replace literals (‘N/A’, ‘cores’) with translatable strings.Use t(...) so locales can translate and handle pluralization.
- value: os_type || 'N/A' + value: os_type || t('common.notAvailable') @@ - value: hostname || 'N/A' + value: hostname || t('common.notAvailable') @@ - value: cpu_info || 'N/A' + value: cpu_info || t('common.notAvailable') @@ - value: cpu_cores > 0 ? `${cpu_cores} cores` : 'N/A' + value: cpu_cores > 0 + ? t('dashboard.system.values.cores', { count: String(cpu_cores) }) + : t('common.notAvailable') @@ - value: kernel_version || 'N/A' + value: kernel_version || t('common.notAvailable') @@ - value: architecture || 'N/A' + value: architecture || t('common.notAvailable')Add the keys if missing:
- common.notAvailable
 - dashboard.system.values.cores (e.g., "{count} cores"). Based on learnings.
 Also applies to: 49-57, 65-67
4-4: Remove unused imports.Drop unused icons to keep bundle lean.
-import { Server, Cpu, HardDrive, Activity, Terminal, Box, CpuIcon, ScreenShare, ServerCog } from 'lucide-react'; +import { Server, HardDrive, Activity, Terminal, Box, CpuIcon, ScreenShare, ServerCog } from 'lucide-react';
80-83: Prefer stable keys over array index.Use item.label as key to reduce re-render churn on reorder.
- {systemInfoItems.map((item, index) => ( - <div key={index} className="flex items-start gap-3 p-2 rounded-lg hover:bg-muted/50 transition-colors"> + {systemInfoItems.map((item) => ( + <div key={String(item.label)} className="flex items-start gap-3 p-2 rounded-lg hover:bg-muted/50 transition-colors">view/app/dashboard/components/system/load-average.tsx (1)
42-45: Externalize UI text to i18n.Axis labels and summary labels should use t(...). Optionally pass localized labels into chart-data helpers.
- yAxisLabel="Load" - xAxisLabel="Time Period" + yAxisLabel={t('dashboard.load.yAxis')} + xAxisLabel={t('dashboard.load.xAxis')} @@ - <TypographyMuted className="text-xs">1 min</TypographyMuted> + <TypographyMuted className="text-xs">{t('dashboard.load.oneMin')}</TypographyMuted> @@ - <TypographyMuted className="text-xs">5 min</TypographyMuted> + <TypographyMuted className="text-xs">{t('dashboard.load.fiveMin')}</TypographyMuted> @@ - <TypographyMuted className="text-xs">15 min</TypographyMuted> + <TypographyMuted className="text-xs">{t('dashboard.load.fifteenMin')}</TypographyMuted>Optional follow-up: allow createLoadAverageChartData/config to accept labels from t(...). Based on learnings.
Also applies to: 53-69
view/app/dashboard/components/system/memory-usage.tsx (1)
45-47: Externalize memory labels to i18n.Replace hardcoded “Used”, “Free”, “Total” with t(...); centerLabel should be localized too.
- value: `${memory.percentage.toFixed(1)}%`, - subLabel: 'Used' + value: `${memory.percentage.toFixed(1)}%`, + subLabel: t('dashboard.memory.usedShort') @@ - <TypographyMuted> - Used: {formatGB(memory.used)} GB - </TypographyMuted> + <TypographyMuted> + {t('dashboard.memory.usedWithUnit', { value: formatGB(memory.used) })} + </TypographyMuted> @@ - <TypographyMuted> - Free: {formatGB(freeMemory)} GB - </TypographyMuted> + <TypographyMuted> + {t('dashboard.memory.freeWithUnit', { value: formatGB(freeMemory) })} + </TypographyMuted> @@ - <TypographyMuted className="text-xs text-center"> - Total: {formatGB(memory.total)} GB - </TypographyMuted> + <TypographyMuted className="text-xs text-center"> + {t('dashboard.memory.totalWithUnit', { value: formatGB(memory.total) })} + </TypographyMuted>Add keys if missing:
- dashboard.memory.usedShort
 - dashboard.memory.usedWithUnit, freeWithUnit, totalWithUnit (e.g., “Used: {value} GB”). Based on learnings.
 Also applies to: 59-67, 71-74
view/app/dashboard/components/system/utils.ts (1)
5-7: Missing unit suffixes in format utilities.The function names
formatGBandformatPercentagesuggest they should include unit suffixes, but they only format the numeric value. Consider either:
- Appending the unit suffix (recommended):
 ${value.toFixed(2)} GBand${value.toFixed(1)}%- Renaming to clarify they only format numbers:
 formatNumberortoFixedDecimalsApply this diff to add unit suffixes:
-export const formatGB = (value: number): string => `${value.toFixed(2)}`; -export const formatPercentage = (value: number): string => `${value.toFixed(1)}`; +export const formatGB = (value: number): string => `${value.toFixed(2)} GB`; +export const formatPercentage = (value: number): string => `${value.toFixed(1)}%`;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
api/api/versions.json(1 hunks)view/app/dashboard/components/system/cpu-usage.tsx(1 hunks)view/app/dashboard/components/system/disk-usage.tsx(2 hunks)view/app/dashboard/components/system/load-average.tsx(1 hunks)view/app/dashboard/components/system/memory-usage.tsx(1 hunks)view/app/dashboard/components/system/skeletons/cpu-usage.tsx(1 hunks)view/app/dashboard/components/system/skeletons/disk-usage.tsx(1 hunks)view/app/dashboard/components/system/skeletons/index.tsx(1 hunks)view/app/dashboard/components/system/skeletons/load-average.tsx(1 hunks)view/app/dashboard/components/system/skeletons/memory-usage.tsx(1 hunks)view/app/dashboard/components/system/skeletons/system-info.tsx(1 hunks)view/app/dashboard/components/system/system-info.tsx(1 hunks)view/app/dashboard/components/system/system-stats.tsx(2 hunks)view/app/dashboard/components/system/utils.ts(1 hunks)view/app/dashboard/components/utils/constants.ts(1 hunks)view/app/dashboard/hooks/use-dashboard.ts(1 hunks)view/app/dashboard/page.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- view/app/dashboard/hooks/use-dashboard.ts
 - view/app/dashboard/components/system/cpu-usage.tsx
 - api/api/versions.json
 
🧰 Additional context used
🧬 Code graph analysis (12)
view/app/dashboard/components/system/skeletons/cpu-usage.tsx (4)
view/components/ui/typography.tsx (1)
TypographyMuted(114-122)view/app/dashboard/hooks/use-system-metric.ts (1)
useSystemMetric(21-41)view/app/dashboard/components/utils/constants.ts (1)
DEFAULT_METRICS(12-33)view/app/dashboard/components/system/system-metric-card.tsx (1)
SystemMetricCard(16-38)
view/app/dashboard/components/system/skeletons/disk-usage.tsx (5)
view/hooks/use-translation.ts (1)
useTranslation(18-74)view/components/ui/typography.tsx (1)
TypographySmall(99-112)view/app/dashboard/hooks/use-system-metric.ts (1)
useSystemMetric(21-41)view/app/dashboard/components/utils/constants.ts (1)
DEFAULT_METRICS(12-33)view/app/dashboard/components/system/system-metric-card.tsx (1)
SystemMetricCard(16-38)
view/app/dashboard/components/system/skeletons/system-info.tsx (3)
view/app/dashboard/components/system/skeletons/index.tsx (1)
SystemInfoCardSkeleton(5-5)view/hooks/use-translation.ts (1)
useTranslation(18-74)view/components/ui/typography.tsx (1)
TypographySmall(99-112)
view/app/dashboard/components/system/skeletons/memory-usage.tsx (3)
view/app/dashboard/hooks/use-system-metric.ts (1)
useSystemMetric(21-41)view/app/dashboard/components/utils/constants.ts (1)
DEFAULT_METRICS(12-33)view/app/dashboard/components/system/system-metric-card.tsx (1)
SystemMetricCard(16-38)
view/app/dashboard/components/system/system-stats.tsx (1)
view/components/ui/draggable-grid.tsx (2)
DraggableItem(26-30)DraggableGrid(93-235)
view/app/dashboard/components/system/system-info.tsx (3)
view/redux/types/monitor.ts (1)
SystemStatsType(69-81)view/hooks/use-translation.ts (1)
useTranslation(18-74)view/app/dashboard/components/system/skeletons/system-info.tsx (1)
SystemInfoCardSkeleton(10-36)
view/app/dashboard/components/system/disk-usage.tsx (5)
view/app/dashboard/hooks/use-system-metric.ts (1)
useSystemMetric(21-41)view/app/dashboard/components/utils/constants.ts (1)
DEFAULT_METRICS(12-33)view/app/dashboard/components/system/system-metric-card.tsx (1)
SystemMetricCard(16-38)view/app/dashboard/components/system/skeletons/disk-usage.tsx (1)
DiskUsageCardSkeletonContent(12-71)view/app/dashboard/components/system/utils.ts (1)
formatPercentage(7-7)
view/app/dashboard/components/system/memory-usage.tsx (8)
view/redux/types/monitor.ts (1)
SystemStatsType(69-81)view/app/dashboard/hooks/use-system-metric.ts (1)
useSystemMetric(21-41)view/app/dashboard/components/utils/constants.ts (2)
DEFAULT_METRICS(12-33)CHART_COLORS(2-9)view/app/dashboard/components/system/utils.ts (3)
createMemoryChartData(47-61)createMemoryChartConfig(63-72)formatGB(6-6)view/app/dashboard/components/system/system-metric-card.tsx (1)
SystemMetricCard(16-38)view/app/dashboard/components/system/skeletons/memory-usage.tsx (1)
MemoryUsageCardSkeletonContent(10-33)view/components/ui/doughnut-chart-component.tsx (1)
DoughnutChartComponent(25-82)view/components/ui/typography.tsx (1)
TypographyMuted(114-122)
view/app/dashboard/components/system/skeletons/load-average.tsx (4)
view/components/ui/typography.tsx (1)
TypographyMuted(114-122)view/app/dashboard/hooks/use-system-metric.ts (1)
useSystemMetric(21-41)view/app/dashboard/components/utils/constants.ts (1)
DEFAULT_METRICS(12-33)view/app/dashboard/components/system/system-metric-card.tsx (1)
SystemMetricCard(16-38)
view/app/dashboard/page.tsx (3)
view/app/dashboard/hooks/use-dashboard.ts (1)
useDashboard(10-78)view/app/dashboard/components/smtp-banner.tsx (1)
SMTPBanner(13-51)view/components/ui/draggable-grid.tsx (2)
DraggableItem(26-30)DraggableGrid(93-235)
view/app/dashboard/components/system/load-average.tsx (7)
view/redux/types/monitor.ts (1)
SystemStatsType(69-81)view/app/dashboard/hooks/use-system-metric.ts (1)
useSystemMetric(21-41)view/app/dashboard/components/system/utils.ts (2)
createLoadAverageChartData(10-30)createLoadAverageChartConfig(32-45)view/app/dashboard/components/system/system-metric-card.tsx (1)
SystemMetricCard(16-38)view/app/dashboard/components/system/skeletons/load-average.tsx (1)
LoadAverageCardSkeletonContent(11-33)view/components/ui/bar-chart-component.tsx (1)
BarChartComponent(22-80)view/components/ui/typography.tsx (2)
TypographyMuted(114-122)TypographySmall(99-112)
view/app/dashboard/components/system/utils.ts (3)
view/components/ui/bar-chart-component.tsx (1)
BarChartDataItem(7-11)view/app/dashboard/components/utils/constants.ts (1)
CHART_COLORS(2-9)view/components/ui/doughnut-chart-component.tsx (1)
DoughnutChartDataItem(7-11)
🔇 Additional comments (7)
view/app/dashboard/components/system/system-stats.tsx (1)
31-50: LGTM: Clean draggable grid integration.The draggable grid implementation is well-structured with proper item IDs and components. The
storageKeyensures layout persistence across sessions.view/app/dashboard/components/system/skeletons/system-info.tsx (1)
10-36: LGTM: Skeleton structure matches system info card.The skeleton correctly generates 8 placeholder items matching the expanded system info fields (OS, hostname, CPU, cores, memory, kernel, uptime, architecture) with a responsive grid layout.
view/app/dashboard/components/system/skeletons/index.tsx (1)
1-5: LGTM: Clean barrel exports for skeleton components.All skeleton components are properly re-exported. Note that
SystemInfoCardSkeletondoesn't export a separateContentcomponent, which is appropriate given its integrated structure.view/app/dashboard/components/system/skeletons/load-average.tsx (1)
11-52: LGTM: Load average skeleton structure is appropriate.The skeleton correctly represents the load average card with a chart placeholder and three standard time intervals (1, 5, 15 minutes). The layout and structure align well with typical load average visualizations.
view/app/dashboard/components/system/disk-usage.tsx (1)
27-31: Verify disk.percentage scale and add scaling if needed.Code inspection confirms the concern: line 44 uses
style={{ width:${disk.percentage}%}}which assumes a 0–100 scale. If the backend sends 0–1, both the width bar (line 44) and percentage display text (line 52, viaformatPercentage) will be incorrect.The
formatPercentageutility does not scale—it only applies.toFixed(1). TheDiskStatstype specifiespercentage: numberwith no scale documentation.Verify what scale your backend actually sends in the
disk.percentagefield. If it's 0–1, multiply by 100 before use at both line 44 and before passing toformatPercentageat line 52.view/app/dashboard/components/system/utils.ts (2)
1-3: LGTM!The imports are clean and necessary for the utility functions below.
78-80: Good defensive coding.The empty array guard prevents runtime errors when no CPU core data is available.
| className={`h-2 rounded-full bg-primary`} | ||
| style={{ width: `${disk.percentage}%` }} | ||
| /> | ||
| </div> | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clamp progress width and add a11y on progress container.
Prevent overflow when percentage is out of range and expose progress semantics for screen readers.
-        <div className="w-full h-2 bg-gray-200 rounded-full">
+        <div
+          className="w-full h-2 bg-gray-200 rounded-full"
+          role="progressbar"
+          aria-valuemin={0}
+          aria-valuemax={100}
+          aria-valuenow={Math.round(Math.max(0, Math.min(100, disk.percentage)))}
+          aria-label={t('dashboard.disk.percentageAria')}
+        >
           <div
             className={`h-2 rounded-full bg-primary`}
-            style={{ width: `${disk.percentage}%` }}
+            style={{ width: `${Math.max(0, Math.min(100, disk.percentage))}%` }}
           />
         </div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| className={`h-2 rounded-full bg-primary`} | |
| style={{ width: `${disk.percentage}%` }} | |
| /> | |
| </div> | |
| <div | |
| className="w-full h-2 bg-gray-200 rounded-full" | |
| role="progressbar" | |
| aria-valuemin={0} | |
| aria-valuemax={100} | |
| aria-valuenow={Math.round(Math.max(0, Math.min(100, disk.percentage)))} | |
| aria-label={t('dashboard.disk.percentageAria')} | |
| > | |
| <div | |
| className={`h-2 rounded-full bg-primary`} | |
| style={{ width: `${Math.max(0, Math.min(100, disk.percentage))}%` }} | |
| /> | |
| </div> | 
🤖 Prompt for AI Agents
In view/app/dashboard/components/system/disk-usage.tsx around lines 43 to 46,
the progress bar sets width directly from disk.percentage which can overflow if
out of 0–100 range and the container lacks accessibility semantics; clamp the
percentage value to a safe number between 0 and 100 (coerce to Number and
default to 0 if invalid) and use that clamped value for the inline width, and
add ARIA progress semantics to the progress container (role="progressbar",
aria-valuemin="0", aria-valuemax="100", aria-valuenow={clamped} plus an
aria-label or aria-labelledby) so screen readers can read the progress.
| icon: <Activity className="h-4 w-4 text-emerald-500" />, | ||
| label: t('dashboard.system.labels.uptime'), | ||
| value: load.uptime?.replaceAll(/([hms])(\d)/g, '$1 $2') | ||
| }, | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix uptime spacing regex (bug).
Current regex inserts space between unit and next digit; should insert space between digit and unit.
-      value: load.uptime?.replaceAll(/([hms])(\d)/g, '$1 $2')
+      value: load.uptime?.replaceAll(/(\d)([hms])/g, '$1 $2')🤖 Prompt for AI Agents
In view/app/dashboard/components/system/system-info.tsx around lines 59 to 62,
the uptime spacing regex currently matches a unit then a digit and inserts a
space in the wrong place; change the pattern to match a digit followed by a unit
and replace with digit + space + unit (e.g. use /(\d)([hms])/g with replacement
'$1 $2') so spacing becomes "1 h 2 m" instead of "1h 2m".
| // Chart data transformers | ||
| export const createLoadAverageChartData = (load: { | ||
| oneMin: number; | ||
| fiveMin: number; | ||
| fifteenMin: number; | ||
| }): BarChartDataItem[] => [ | ||
| { | ||
| name: '1 min', | ||
| value: load.oneMin, | ||
| fill: CHART_COLORS.blue, | ||
| }, | ||
| { | ||
| name: '5 min', | ||
| value: load.fiveMin, | ||
| fill: CHART_COLORS.green, | ||
| }, | ||
| { | ||
| name: '15 min', | ||
| value: load.fifteenMin, | ||
| fill: CHART_COLORS.orange, | ||
| }, | ||
| ]; | ||
| 
               | 
          ||
| export const createLoadAverageChartConfig = () => ({ | ||
| oneMin: { | ||
| label: '1 min', | ||
| color: CHART_COLORS.blue, | ||
| }, | ||
| fiveMin: { | ||
| label: '5 min', | ||
| color: CHART_COLORS.green, | ||
| }, | ||
| fifteenMin: { | ||
| label: '15 min', | ||
| color: CHART_COLORS.orange, | ||
| }, | ||
| }); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded labels should be internationalized.
The load average labels ("1 min", "5 min", "15 min") are hardcoded strings, but the PR description states that locale files were updated with translations for new system metadata labels. These user-facing chart labels should use the i18n system to support the English, Spanish, French, Kannada, and Malayalam translations mentioned in the PR.
Consider refactoring these functions to accept translated labels as parameters or use a translation hook/utility:
export const createLoadAverageChartData = (
  load: { oneMin: number; fiveMin: number; fifteenMin: number },
  labels: { oneMin: string; fiveMin: string; fifteenMin: string }
): BarChartDataItem[] => [
  {
    name: labels.oneMin,
    value: load.oneMin,
    fill: CHART_COLORS.blue,
  },
  // ... rest of the items
];🤖 Prompt for AI Agents
In view/app/dashboard/components/system/utils.ts around lines 9 to 45, the chart
labels "1 min", "5 min", "15 min" are hardcoded; change the API so labels are
provided by the i18n system instead of literal strings: update
createLoadAverageChartData to accept a labels parameter ({ oneMin, fiveMin,
fifteenMin }) and use those for the name fields (preserve types for
BarChartDataItem), and update createLoadAverageChartConfig to accept or return
the same translated labels and colors (or accept a labels parameter) so the
config uses translated label strings; finally, update all call sites to pass
translated strings from the app’s i18n utility/hook (e.g. t('...')) and adjust
types accordingly.
| export const createMemoryChartData = ( | ||
| used: number, | ||
| free: number | ||
| ): DoughnutChartDataItem[] => [ | ||
| { | ||
| name: 'Used', | ||
| value: used, | ||
| fill: CHART_COLORS.blue, | ||
| }, | ||
| { | ||
| name: 'Free', | ||
| value: free, | ||
| fill: CHART_COLORS.green, | ||
| }, | ||
| ]; | ||
| 
               | 
          ||
| export const createMemoryChartConfig = () => ({ | ||
| used: { | ||
| label: 'Used Memory', | ||
| color: CHART_COLORS.blue, | ||
| }, | ||
| free: { | ||
| label: 'Free Memory', | ||
| color: CHART_COLORS.green, | ||
| }, | ||
| }); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded labels should be internationalized.
Similar to the load average chart, the memory chart labels ("Used", "Free", "Used Memory", "Free Memory") are hardcoded. These should also be internationalized to support the multiple locales mentioned in the PR.
🤖 Prompt for AI Agents
In view/app/dashboard/components/system/utils.ts around lines 47 to 72, the
chart labels "Used", "Free", "Used Memory", and "Free Memory" are hardcoded;
replace them with i18n keys and calls to the project's translation helper (e.g.,
import the existing t/i18n function) so the DoughnutChartDataItem names and the
createMemoryChartConfig labels use translated strings (use clear keys such as
"memory.used", "memory.free", "memory.usedLabel", "memory.freeLabel"), ensure
the import is added at the top, and keep types intact so the chart receives
translated string values at runtime.
| const colors = [ | ||
| CHART_COLORS.blue, | ||
| CHART_COLORS.green, | ||
| CHART_COLORS.orange, | ||
| CHART_COLORS.purple, | ||
| CHART_COLORS.red, | ||
| CHART_COLORS.yellow, | ||
| ]; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Extract duplicated color palette to a constant.
The same color array is defined in both createCPUChartData (lines 83-90) and createCPUChartConfig (lines 101-108). This violates the DRY principle and could lead to inconsistencies if colors are updated in only one location.
Apply this diff to extract the palette:
+// CPU color palette
+const CPU_COLORS = [
+  CHART_COLORS.blue,
+  CHART_COLORS.green,
+  CHART_COLORS.orange,
+  CHART_COLORS.purple,
+  CHART_COLORS.red,
+  CHART_COLORS.yellow,
+] as const;
+
 // CPU Chart utilities
 export const createCPUChartData = (
   perCore: Array<{ core_id: number; usage: number }>
 ): BarChartDataItem[] => {
   if (!perCore || perCore.length === 0) {
     return [];
   }
 
-  // Define a color palette for cores (cycle through colors)
-  const colors = [
-    CHART_COLORS.blue,
-    CHART_COLORS.green,
-    CHART_COLORS.orange,
-    CHART_COLORS.purple,
-    CHART_COLORS.red,
-    CHART_COLORS.yellow,
-  ];
-
   return perCore.map((core) => ({
     name: `Core ${core.core_id}`,
     value: core.usage,
-    fill: colors[core.core_id % colors.length],
+    fill: CPU_COLORS[core.core_id % CPU_COLORS.length],
   }));
 };
 
 export const createCPUChartConfig = (coreCount: number) => {
   const config: Record<string, { label: string; color: string }> = {};
-  const colors = [
-    CHART_COLORS.blue,
-    CHART_COLORS.green,
-    CHART_COLORS.orange,
-    CHART_COLORS.purple,
-    CHART_COLORS.red,
-    CHART_COLORS.yellow,
-  ];
 
   for (let i = 0; i < coreCount; i++) {
     config[`core${i}`] = {
       label: `Core ${i}`,
-      color: colors[i % colors.length],
+      color: CPU_COLORS[i % CPU_COLORS.length],
     };
   }
 
   return config;
 };Also applies to: 101-108
🤖 Prompt for AI Agents
In view/app/dashboard/components/system/utils.ts around lines 83-90 (and also
101-108), the CHART_COLORS array is duplicated between createCPUChartData and
createCPUChartConfig; extract the palette to a single top-level constant (e.g.,
const CPU_CHART_PALETTE = [CHART_COLORS.blue, CHART_COLORS.green,
CHART_COLORS.orange, CHART_COLORS.purple, CHART_COLORS.red, CHART_COLORS.yellow]
placed near other module-level constants), mark it readonly if applicable, and
replace the inline arrays in both functions to reference CPU_CHART_PALETTE so
both use the same source of truth.
| ]; | ||
| 
               | 
          ||
| return perCore.map((core) => ({ | ||
| name: `Core ${core.core_id}`, | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded "Core" prefix should be internationalized.
The "Core" prefix in the CPU chart labels is hardcoded and appears in user-facing charts. Like the other chart labels, this should be internationalized to support the multiple locales mentioned in the PR.
Consider accepting a translated label prefix as a parameter:
export const createCPUChartData = (
  perCore: Array<{ core_id: number; usage: number }>,
  coreLabel: string = 'Core' // default for backward compatibility
): BarChartDataItem[] => {
  // ...
  return perCore.map((core) => ({
    name: `${coreLabel} ${core.core_id}`,
    value: core.usage,
    fill: CPU_COLORS[core.core_id % CPU_COLORS.length],
  }));
};Also applies to: 112-112
🤖 Prompt for AI Agents
In view/app/dashboard/components/system/utils.ts around lines 93 and 112, the
CPU chart label currently hardcodes the "Core" prefix; change the
createCPUChartData function to accept a coreLabel parameter (defaulting to
'Core' for backward compatibility) and use that parameter when building the name
(`${coreLabel} ${core.core_id}`), update both occurrences at lines ~93 and ~112,
and update any callers to pass a translated label from the i18n layer (e.g.,
t('coreLabel')) so charts are localized while keeping the default behavior for
callers that don't provide the label.
| percentage: 0 as number, | ||
| used: 0 as number, | ||
| total: 0 as number, | ||
| allMounts: [] as any[] | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Weak typing: allMounts uses any[].
Line 31 uses any[] for allMounts, which bypasses type safety. Define a proper type for mount entries based on the actual structure (e.g., { mountPoint: string; total: number; used: number; percentage: number }[]).
Example fix:
+interface MountInfo {
+  mountPoint: string;
+  total: number;
+  used: number;
+  percentage: number;
+}
+
 export const DEFAULT_METRICS = {
   // ...
   disk: {
     percentage: 0 as number,
     used: 0 as number,
     total: 0 as number,
-    allMounts: [] as any[]
+    allMounts: [] as MountInfo[]
   }
 };Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In view/app/dashboard/components/utils/constants.ts around line 31, replace the
weak any[] typing for allMounts with a concrete mount-entry type (for example
export an interface/type like { mountPoint: string; total: number; used: number;
percentage: number } and then declare allMounts: MountEntry[]). Update the
declaration to use the new type, export the type if reused elsewhere, and fix
any places that read/write allMounts to conform to the fields and types (adjust
parsing/initialization where necessary).
| const MonitoringSection = ({ | ||
| systemStats, | ||
| containersData, | ||
| t | ||
| t, | ||
| layoutResetKey, | ||
| onLayoutChange | ||
| }: { | ||
| systemStats: any; | ||
| containersData: any; | ||
| t: any; | ||
| layoutResetKey: number; | ||
| onLayoutChange: () => void; | ||
| }) => { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Replace any in MonitoringSection props with concrete types.
Tighten types for safer refactors and editor tooling.
-import { useDashboard } from './hooks/use-dashboard';
+import { useDashboard } from './hooks/use-dashboard';
+import type { SystemStatsType } from '@/redux/types/monitor';
+// TODO: replace `unknown[]` with your concrete container type (e.g., ContainerData[])
+
@@
-}: {
-  systemStats: any;
-  containersData: any;
-  t: any;
-  layoutResetKey: number;
-  onLayoutChange: () => void;
-}) => {
+}: {
+  systemStats: SystemStatsType | null;
+  containersData: unknown[];
+  t: (key: string) => string;
+  layoutResetKey: number;
+  onLayoutChange: () => void;
+}) => {To locate an existing container type, run:
🏁 Script executed:
#!/bin/bash
# Find candidate container types/interfaces in the repo
rg -nP --type=ts -C3 '\b(interface|type)\s+Container\w*'Length of output: 2484
I need to directly inspect the codebase to verify the suggested types. Let me check the monitor.ts file and how MonitoringSection is used.
#!/bin/bash
# Check contents of monitor.ts to verify SystemStatsType exists
echo "=== Contents of view/redux/types/monitor.ts ==="
cat view/redux/types/monitor.ts
echo -e "\n=== MonitoringSection definition in dashboard page ==="
# Find and extract the MonitoringSection definition
rg -n 'const MonitoringSection' view/app/dashboard/page.tsx -A 30
echo -e "\n=== Check imports in dashboard page ==="
head -30 view/app/dashboard/page.tsx🏁 Script executed:
#!/bin/bash
# Check contents of monitor.ts to verify SystemStatsType exists
echo "=== Contents of view/redux/types/monitor.ts ==="
cat view/redux/types/monitor.ts
echo -e "\n=== MonitoringSection definition in dashboard page ==="
# Find MonitoringSection definition and its surroundings
rg -n 'const MonitoringSection' view/app/dashboard/page.tsx -A 25
echo -e "\n=== Check how MonitoringSection is called ==="
rg -n '<MonitoringSection' view/app/dashboard/page.tsx -B 5 -A 5
echo -e "\n=== Check imports in dashboard page (first 40 lines) ==="
head -40 view/app/dashboard/page.tsxLength of output: 4554
🏁 Script executed:
#!/bin/bash
# Check use-dashboard hook return types
echo "=== use-dashboard hook (check file location and content) ==="
find view/app/dashboard -name "*use-dashboard*" -type f
echo -e "\n=== use-dashboard hook content ==="
rg -n 'useDashboard|systemStats|containersData' view/app/dashboard/hooks/use-dashboard.ts -A 3 -B 1 | head -80
echo -e "\n=== use-translation hook content ==="
rg -n 'export.*useTranslation|return.*{.*t' view/hooks/use-translation.ts -A 5 -B 2 | head -60
echo -e "\n=== Check return type of useDashboard ==="
rg -n 'return.*{' view/app/dashboard/hooks/use-dashboard.ts -A 15 | tail -30Length of output: 923
🏁 Script executed:
#!/bin/bash
# Check useMonitor hook
echo "=== useMonitor hook location ==="
find view -name "*use-monitor*" -type f 2>/dev/null
echo -e "\n=== useMonitor hook definition ==="
rg -n 'export.*useMonitor' view -A 30 --type=ts
echo -e "\n=== useTranslation hook definition ==="
cat view/hooks/use-translation.ts
echo -e "\n=== Check types returned from useMonitor ==="
rg -n 'systemStats\|containersData' view/hooks/use-monitor.ts -B 2 -A 2Length of output: 2342
🏁 Script executed:
#!/bin/bash
echo "=== useMonitor hook definition ==="
cat view/app/dashboard/hooks/use-monitor.tsLength of output: 3002
Use concrete types: ContainerData[] for containers and stronger typing for the t function.
The suggested types are partially correct but need refinement. Based on verification:
systemStats: SystemStatsType | null— ✅ Correct, matchesuseMonitor()return type exactlycontainersData: unknown[]— ✅ Should beContainerData[]instead. TheContainerDatatype is already exported from@/redux/types/monitorand is whatuseMonitor()actually returns (useState<ContainerData[]>), so use it directlyt: (key: string) => string— Acceptable but incomplete. The actual signature fromuseTranslation()is(key: translationKey, params?: Record<string, string>): stringfor better editor autocompletion and type safety
Update the suggested diff to use ContainerData[] directly instead of unknown[], removing the TODO comment:
-}: {
-  systemStats: any;
-  containersData: any;
-  t: any;
-  layoutResetKey: number;
-  onLayoutChange: () => void;
-}) => {
+}: {
+  systemStats: SystemStatsType | null;
+  containersData: ContainerData[];
+  t: (key: translationKey, params?: Record<string, string>) => string;
+  layoutResetKey: number;
+  onLayoutChange: () => void;
+}) => {Add imports:
import type { SystemStatsType, ContainerData } from '@/redux/types/monitor';
import type { translationKey } from '@/hooks/use-translation';🤖 Prompt for AI Agents
In view/app/dashboard/page.tsx around lines 153 to 165, replace the loose
any/unknown types and TODO with concrete imports and types: import type {
SystemStatsType, ContainerData } from '@/redux/types/monitor' and import type {
translationKey } from '@/hooks/use-translation', change the prop types to
systemStats: SystemStatsType | null, containersData: ContainerData[], and t:
(key: translationKey, params?: Record<string,string>) => string, and remove the
TODO comment; ensure you update the function signature and add the new type-only
imports at the top of the file.

Description
This PR significantly enhances the dashboard with an improved user experience through drag-and-drop card reordering, interactive charts, and expanded system information.
Key Changes
Dashboard UX Improvements
Enhanced Visualizations
Extended System Metrics
Added new system information fields:
Backend Changes
SystemStatsstruct in Go backend to include hostname, CPU cores, kernel version, and architectureInternationalization
New Components
DraggableGrid: Reusable drag-and-drop grid component with persistenceBarChartComponent: Configurable bar chart component using rechartsDoughnutChartComponent: Donut chart component for circular visualizationsDependencies
@dnd-kit/core,@dnd-kit/sortable,@dnd-kit/utilitiesfor drag-and-drop functionalityScope of Change
Testing Notes
Summary by CodeRabbit
Release Notes