@@ -12,7 +12,6 @@ import { PaneLoader, FreshnessControl } from '@skyhook-io/k8s-ui'
1212import { CostTrendChart } from './CostTrendChart'
1313import {
1414 COST_HOURS_PER_MONTH ,
15- formatCost ,
1615 formatCostPerHour ,
1716 formatProjectedDailyRate ,
1817 formatProjectedMonthlyCost ,
@@ -29,6 +28,8 @@ interface CostViewProps {
2928 onOpenResource ?: ( resource : SelectedResource ) => void
3029}
3130
31+ const SYSTEM_COST_NAMESPACES = new Set ( [ 'kube-system' , 'kube-public' , 'kube-node-lease' ] )
32+
3233export function CostView ( { onBack, onOpenResource } : CostViewProps ) {
3334 const { data, isLoading, isFetching, dataUpdatedAt, refetch } = useOpenCostSummary ( )
3435 const { data : nodeData } = useOpenCostNodes ( )
@@ -119,6 +120,7 @@ export function CostView({ onBack, onOpenResource }: CostViewProps) {
119120 const totalStorage = data . totalStorageCost ?? 0
120121 const hasStorage = totalStorage > 0
121122 const hasEfficiency = ( data . clusterEfficiency ?? 0 ) > 0
123+ const hasSystemNamespaces = namespaces . some ( ( ns ) => isSystemCostNamespace ( ns . name ) )
122124
123125 // Compute split percentages (CPU + Memory + optional Storage)
124126 const allocTotal = totalCpu + totalMem + totalStorage
@@ -131,7 +133,7 @@ export function CostView({ onBack, onOpenResource }: CostViewProps) {
131133
132134 return (
133135 < div className = "flex-1 overflow-y-auto" >
134- < div className = "mx-auto w-full max-w-[1600px ] px-6 py-6 space-y-6" >
136+ < div className = "mx-auto w-full max-w-[1920px ] px-6 py-6 space-y-6" >
135137 { /* Header */ }
136138 < div className = "flex items-center justify-between" >
137139 < div className = "flex items-center gap-3" >
@@ -205,24 +207,24 @@ export function CostView({ onBack, onOpenResource }: CostViewProps) {
205207 < span className = "text-xs font-medium text-theme-text-secondary" > Cluster Resource Cost</ span >
206208 < div className = "flex items-center gap-4 text-xs text-theme-text-tertiary" >
207209 < span className = "flex items-center gap-1.5" >
208- < span className = "w-2.5 h-2.5 rounded-sm bg-blue-500 " />
209- CPU { formatCostPerHour ( totalCpu ) }
210+ < span className = "w-2.5 h-2.5 rounded-sm bg-accent " />
211+ CPU { formatProjectedMonthlyRate ( totalCpu ) }
210212 </ span >
211213 < span className = "flex items-center gap-1.5" >
212- < span className = "w-2.5 h-2.5 rounded-sm bg-purple -500" />
213- Memory { formatCostPerHour ( totalMem ) }
214+ < span className = "w-2.5 h-2.5 rounded-sm bg-amber -500" />
215+ Memory { formatProjectedMonthlyRate ( totalMem ) }
214216 </ span >
215217 { hasStorage && (
216218 < span className = "flex items-center gap-1.5" >
217219 < span className = "w-2.5 h-2.5 rounded-sm bg-teal-500" />
218- Storage { formatCostPerHour ( totalStorage ) }
220+ Storage { formatProjectedMonthlyRate ( totalStorage ) }
219221 </ span >
220222 ) }
221223 </ div >
222224 </ div >
223225 < div className = "h-3 rounded-full overflow-hidden bg-theme-hover flex" >
224- < div className = "h-full bg-blue-500 transition-all duration-300" style = { { width : `${ cpuPct } %` } } />
225- < div className = "h-full bg-purple -500 transition-all duration-300" style = { { width : `${ memPct } %` } } />
226+ < div className = "h-full bg-accent transition-all duration-300" style = { { width : `${ cpuPct } %` } } />
227+ < div className = "h-full bg-amber -500 transition-all duration-300" style = { { width : `${ memPct } %` } } />
226228 { hasStorage && (
227229 < div className = "h-full bg-teal-500 transition-all duration-300" style = { { width : `${ storagePct } %` } } />
228230 ) }
@@ -243,9 +245,10 @@ export function CostView({ onBack, onOpenResource }: CostViewProps) {
243245 < span className = "text-xs text-theme-text-tertiary" > { namespaces . length } namespaces</ span >
244246 </ div >
245247 </ div >
248+ { hasSystemNamespaces && < SystemNamespacesCostNote /> }
246249
247250 { /* Table header */ }
248- < div className = "grid grid-cols-[minmax(180px,1fr)_100px_90px_80px_minmax (160px,1fr)_120px ] gap-2 px-4 py-2 border-b border-theme-border text-[11px] font-medium text-theme-text-tertiary uppercase tracking-wider" >
251+ < div className = "grid grid-cols-[minmax(180px,1fr)_110px_90px_80px_minmax (160px,1fr)_150px ] gap-2 px-4 py-2 border-b border-theme-border text-[11px] font-medium text-theme-text-tertiary uppercase tracking-wider" >
249252 < span > Namespace</ span >
250253 < Tooltip
251254 content = "Projected from current hourly rate — not historical spend"
@@ -261,7 +264,12 @@ export function CostView({ onBack, onOpenResource }: CostViewProps) {
261264 < span className = "cursor-help" > Efficiency</ span >
262265 </ Tooltip >
263266 < span > CPU / Memory</ span >
264- < span className = "text-right" > Cost Split</ span >
267+ < Tooltip
268+ content = "Projected monthly CPU and memory allocation from the current hourly rate"
269+ wrapperClassName = "!block text-right"
270+ >
271+ < span className = "cursor-help" > CPU / Memory/mo*</ span >
272+ </ Tooltip >
265273 </ div >
266274
267275 { /* Namespace rows */ }
@@ -320,7 +328,7 @@ function NamespaceCostRow({
320328 < div >
321329 < button
322330 onClick = { ( ) => setExpanded ( ! expanded ) }
323- className = "w-full grid grid-cols-[minmax(180px,1fr)_100px_90px_80px_minmax (160px,1fr)_120px ] gap-2 px-4 py-2.5 text-left hover:bg-theme-hover/50 transition-colors group"
331+ className = "w-full grid grid-cols-[minmax(180px,1fr)_110px_90px_80px_minmax (160px,1fr)_150px ] gap-2 px-4 py-2.5 text-left hover:bg-theme-hover/50 transition-colors group"
324332 >
325333 < span className = "flex items-center gap-1.5 min-w-0" >
326334 { expanded ? (
@@ -331,6 +339,16 @@ function NamespaceCostRow({
331339 < Tooltip content = { `Namespace ${ ns . name } ` } wrapperClassName = "min-w-0" >
332340 < span className = "block truncate text-sm text-theme-text-primary font-medium" > { ns . name } </ span >
333341 </ Tooltip >
342+ { isSystemCostNamespace ( ns . name ) && (
343+ < Tooltip
344+ content = "Usually platform overhead from Kubernetes and cluster add-ons. Review enabled add-ons before treating this as app waste."
345+ wrapperClassName = "shrink-0"
346+ >
347+ < span className = "rounded bg-theme-elevated px-1.5 py-0.5 text-[10px] font-medium text-theme-text-tertiary" >
348+ system
349+ </ span >
350+ </ Tooltip >
351+ ) }
334352 </ span >
335353 < span className = "text-sm font-medium text-theme-text-primary tabular-nums text-right" >
336354 { formatProjectedMonthlyCost ( ns . hourlyCost ) }
@@ -352,25 +370,50 @@ function NamespaceCostRow({
352370 className = "flex-1 h-2 rounded-full overflow-hidden bg-theme-hover flex"
353371 style = { { maxWidth : `${ Math . max ( barWidth , 3 ) } %` } }
354372 >
355- < div className = "h-full bg-blue-500/70 " style = { { width : `${ cpuPct } %` } } />
356- < div className = "h-full bg-purple -500/70 " style = { { width : `${ memPct } %` } } />
373+ < div className = "h-full bg-accent " style = { { width : `${ cpuPct } %` } } />
374+ < div className = "h-full bg-amber -500" style = { { width : `${ memPct } %` } } />
357375 { hasStorage && ( ns . storageCost ?? 0 ) > 0 && (
358- < div className = "h-full bg-teal-500/70 " style = { { width : `${ 100 - cpuPct - memPct } %` } } />
376+ < div className = "h-full bg-teal-500" style = { { width : `${ 100 - cpuPct - memPct } %` } } />
359377 ) }
360378 </ div >
361379 </ span >
362380 < span className = "text-[11px] text-theme-text-tertiary tabular-nums text-right" >
363- { formatCost ( ns . cpuCost ) } / { formatCost ( ns . memoryCost ) }
364- { hasStorage && ( ns . storageCost ?? 0 ) > 0 && ` / ${ formatCost ( ns . storageCost ?? 0 ) } ` }
381+ { formatProjectedMonthlyCost ( ns . cpuCost ) } / { formatProjectedMonthlyCost ( ns . memoryCost ) }
382+ { hasStorage && ( ns . storageCost ?? 0 ) > 0 && ` / ${ formatProjectedMonthlyCost ( ns . storageCost ?? 0 ) } ` }
365383 </ span >
366384 </ button >
367385
368386 { /* Expanded workload rows */ }
387+ { expanded && isSystemCostNamespace ( ns . name ) && < SystemNamespaceCostNote namespace = { ns . name } /> }
369388 { expanded && < WorkloadRows namespace = { ns . name } onOpenResource = { onOpenResource } /> }
370389 </ div >
371390 )
372391}
373392
393+ function isSystemCostNamespace ( namespace : string ) : boolean {
394+ return SYSTEM_COST_NAMESPACES . has ( namespace )
395+ }
396+
397+ function SystemNamespaceCostNote ( { namespace } : { namespace : string } ) {
398+ return (
399+ < div className = "border-t border-theme-border/30 bg-theme-elevated/30 px-10 py-2 text-xs text-theme-text-tertiary" >
400+ < span className = "font-medium text-theme-text-secondary" > { namespace } </ span > is usually baseline cluster overhead:
401+ Kubernetes components, cloud-provider agents, and installed add-ons. Optimize by reviewing add-ons, logging,
402+ monitoring, or node count rather than deleting system workloads directly.
403+ </ div >
404+ )
405+ }
406+
407+ function SystemNamespacesCostNote ( ) {
408+ return (
409+ < div className = "border-b border-theme-border/50 bg-theme-elevated/30 px-4 py-2 text-xs text-theme-text-tertiary" >
410+ Rows marked < span className = "font-medium text-theme-text-secondary" > system</ span > are usually baseline Kubernetes,
411+ cloud-provider, or add-on overhead. Optimize by reviewing enabled add-ons, telemetry, or node count before
412+ treating them as application waste.
413+ </ div >
414+ )
415+ }
416+
374417function WorkloadRows ( {
375418 namespace,
376419 onOpenResource,
@@ -463,18 +506,18 @@ function WorkloadCostRow({
463506 className = "flex-1 h-1.5 rounded-full overflow-hidden bg-theme-hover flex"
464507 style = { { maxWidth : `${ Math . max ( barWidth , 3 ) } %` } }
465508 >
466- < div className = "h-full bg-blue-500/50 " style = { { width : `${ cpuPct } %` } } />
467- < div className = "h-full bg-purple -500/50 " style = { { width : `${ 100 - cpuPct } %` } } />
509+ < div className = "h-full bg-accent " style = { { width : `${ cpuPct } %` } } />
510+ < div className = "h-full bg-amber -500" style = { { width : `${ 100 - cpuPct } %` } } />
468511 </ div >
469512 </ span >
470513 < span className = "text-[10px] text-theme-text-tertiary tabular-nums text-right" >
471- { formatCost ( wl . cpuCost ) } / { formatCost ( wl . memoryCost ) }
514+ { formatProjectedMonthlyCost ( wl . cpuCost ) } / { formatProjectedMonthlyCost ( wl . memoryCost ) }
472515 </ span >
473516 </ >
474517 )
475518
476519 const rowClass =
477- 'grid grid-cols-[minmax(180px,1fr)_100px_90px_80px_minmax (160px,1fr)_120px ] gap-2 px-4 py-2 text-left'
520+ 'grid grid-cols-[minmax(180px,1fr)_110px_90px_80px_minmax (160px,1fr)_150px ] gap-2 px-4 py-2 text-left'
478521
479522 if ( canOpen && resource ) {
480523 return (
@@ -518,7 +561,7 @@ function NodeCostTable({
518561 </ div >
519562
520563 { /* Table header */ }
521- < div className = "grid grid-cols-[minmax(200px,1fr)_minmax(120px,1fr)_100px_90px_140px ] gap-2 px-4 py-2 border-b border-theme-border text-[11px] font-medium text-theme-text-tertiary uppercase tracking-wider" >
564+ < div className = "grid grid-cols-[minmax(200px,1fr)_minmax(120px,1fr)_110px_90px_150px ] gap-2 px-4 py-2 border-b border-theme-border text-[11px] font-medium text-theme-text-tertiary uppercase tracking-wider" >
522565 < span > Node</ span >
523566 < span > Instance Type</ span >
524567 < Tooltip
@@ -528,7 +571,12 @@ function NodeCostTable({
528571 < span className = "cursor-help" > Projected/mo*</ span >
529572 </ Tooltip >
530573 < span className = "text-right" > Hourly</ span >
531- < span className = "text-right" > CPU / Memory</ span >
574+ < Tooltip
575+ content = "Projected monthly CPU and memory portions of this node's current price"
576+ wrapperClassName = "!block text-right"
577+ >
578+ < span className = "cursor-help" > CPU / Memory/mo*</ span >
579+ </ Tooltip >
532580 </ div >
533581
534582 { /* Node rows */ }
@@ -552,7 +600,7 @@ function NodeCostRow({
552600 const openNode = ( ) => onOpenResource ?.( { kind : 'nodes' , namespace : '' , name : node . name } )
553601
554602 return (
555- < div className = "grid grid-cols-[minmax(200px,1fr)_minmax(120px,1fr)_100px_90px_140px ] gap-2 px-4 py-2.5" >
603+ < div className = "grid grid-cols-[minmax(200px,1fr)_minmax(120px,1fr)_110px_90px_150px ] gap-2 px-4 py-2.5" >
556604 < span className = "flex min-w-0 items-center gap-1.5" >
557605 < Tooltip content = { `Open Node ${ node . name } ` } wrapperClassName = "!block min-w-0" >
558606 { onOpenResource ? (
@@ -591,7 +639,7 @@ function NodeCostRow({
591639 { formatCostPerHour ( node . hourlyCost ) }
592640 </ span >
593641 < span className = "text-[11px] text-theme-text-tertiary tabular-nums text-right" >
594- { formatCost ( node . cpuCost ) } / { formatCost ( node . memoryCost ) }
642+ { formatProjectedMonthlyCost ( node . cpuCost ) } / { formatProjectedMonthlyCost ( node . memoryCost ) }
595643 </ span >
596644 </ div >
597645 )
0 commit comments