@skyhook-io/radar-app 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/package.json +33 -25
- package/src/App.tsx +1449 -382
- package/src/RadarApp.tsx +132 -19
- package/src/api/apiResources.ts +1 -1
- package/src/api/client.argoResourceSync.test.ts +69 -0
- package/src/api/client.delta.test.ts +89 -0
- package/src/api/client.deltaSync.test.ts +216 -0
- package/src/api/client.metrics.test.ts +106 -0
- package/src/api/client.rightsizing.test.ts +32 -0
- package/src/api/client.ts +2730 -271
- package/src/api/client.yaml.test.ts +45 -0
- package/src/api/diagnose.ts +289 -0
- package/src/api/quotas.ts +16 -0
- package/src/api/rbac.ts +57 -0
- package/src/api/timelineSource.test.ts +217 -0
- package/src/api/timelineSource.ts +582 -0
- package/src/components/ConnectionErrorView.tsx +186 -70
- package/src/components/ContextSwitcher.tsx +63 -18
- package/src/components/DebugOverlay.tsx +5 -3
- package/src/components/NamespaceSwitcher.tsx +41 -0
- package/src/components/UserMenu.tsx +69 -21
- package/src/components/applications/ApplicationsView.tsx +936 -0
- package/src/components/audit/AuditSettingsDialog.tsx +79 -17
- package/src/components/audit/AuditView.tsx +65 -62
- package/src/components/compare/CompareViewRoute.tsx +124 -0
- package/src/components/compare/useCompareCandidates.ts +27 -0
- package/src/components/compare/useCompareLauncher.tsx +79 -0
- package/src/components/cost/ApplicationCostTab.test.ts +204 -0
- package/src/components/cost/ApplicationCostTab.tsx +571 -0
- package/src/components/cost/CostTrendChart.tsx +106 -75
- package/src/components/cost/CostView.test.ts +12 -0
- package/src/components/cost/CostView.tsx +507 -223
- package/src/components/cost/CostViewTabs.test.tsx +21 -0
- package/src/components/cost/CostViewTabs.tsx +40 -0
- package/src/components/cost/CurrentAllocationUse.test.ts +21 -0
- package/src/components/cost/CurrentAllocationUse.tsx +126 -0
- package/src/components/cost/WorkloadCostTab.test.ts +153 -0
- package/src/components/cost/WorkloadCostTab.tsx +372 -0
- package/src/components/cost/cloud-console.test.ts +39 -0
- package/src/components/cost/cloud-console.ts +81 -0
- package/src/components/cost/errors.ts +8 -0
- package/src/components/cost/format.test.ts +27 -0
- package/src/components/cost/format.ts +46 -0
- package/src/components/cost/kinds.ts +5 -0
- package/src/components/curl/ServiceCurlButton.tsx +445 -0
- package/src/components/diagnose/AISettings.tsx +147 -0
- package/src/components/diagnose/DiagnoseContext.tsx +495 -0
- package/src/components/diagnose/DiagnoseSurface.tsx +394 -0
- package/src/components/diagnose/Home.tsx +163 -0
- package/src/components/diagnose/InvestigationView.tsx +622 -0
- package/src/components/diagnose/LocalDiagnoseAction.tsx +162 -0
- package/src/components/diagnose/launch.ts +65 -0
- package/src/components/diagnose/parts.tsx +1756 -0
- package/src/components/dock/BottomDock.tsx +2 -3
- package/src/components/dock/DockContext.tsx +1 -0
- package/src/components/dock/TerminalTab.tsx +1 -1
- package/src/components/dock/WorkloadLogsTab.tsx +21 -5
- package/src/components/dock/index.ts +1 -1
- package/src/components/execution/BatchExecutionView.test.ts +170 -0
- package/src/components/execution/BatchExecutionView.tsx +1329 -0
- package/src/components/execution/batch-run-actions.test.ts +48 -0
- package/src/components/execution/batch-run-actions.ts +24 -0
- package/src/components/execution/batch-timeline.test.ts +57 -0
- package/src/components/execution/batch-timeline.ts +46 -0
- package/src/components/execution/execution-definition.test.ts +208 -0
- package/src/components/execution/execution-definition.ts +245 -0
- package/src/components/gitops/ArgoResourceDiffLoader.tsx +23 -0
- package/src/components/gitops/GitOpsView.tsx +1042 -0
- package/src/components/gitops/RevisionMetaChip.tsx +63 -0
- package/src/components/helm/ChartBrowser.tsx +87 -31
- package/src/components/helm/HelmCompareRoute.tsx +1341 -0
- package/src/components/helm/HelmReleaseDrawer.test.ts +17 -0
- package/src/components/helm/HelmReleaseDrawer.tsx +1073 -102
- package/src/components/helm/HelmView.tsx +237 -96
- package/src/components/helm/InstallWizard.tsx +94 -38
- package/src/components/helm/ManifestDiffViewer.tsx +8 -27
- package/src/components/helm/OwnedResources.tsx +34 -59
- package/src/components/helm/RevisionHistory.tsx +52 -3
- package/src/components/helm/RoleGatedPanel.tsx +3 -3
- package/src/components/helm/TrackChartSourceDialog.tsx +185 -0
- package/src/components/helm/ValuesDiffPreview.tsx +17 -7
- package/src/components/helm/ValuesViewer.tsx +49 -53
- package/src/components/helm/helm-utils.ts +4 -0
- package/src/components/home/ActivitySummary.tsx +4 -1
- package/src/components/home/ClusterHealthCard.tsx +56 -42
- package/src/components/home/CostCard.tsx +21 -36
- package/src/components/home/GitOpsControllersCard.tsx +110 -0
- package/src/components/home/HelmSummary.tsx +3 -1
- package/src/components/home/HomeView.tsx +339 -105
- package/src/components/home/MCPSetupDialog.tsx +29 -87
- package/src/components/home/TrafficSummary.tsx +2 -2
- package/src/components/home/mcpToolCatalog.ts +333 -0
- package/src/components/issues/IssuesPane.tsx +151 -0
- package/src/components/logs/LogsViewer.tsx +4 -1
- package/src/components/logs/ScheduledWorkloadLogsViewer.tsx +135 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +4 -1
- package/src/components/nav/PrimaryNavRail.tsx +285 -0
- package/src/components/portforward/PortForwardButton.tsx +118 -47
- package/src/components/portforward/PortForwardManager.tsx +253 -131
- package/src/components/resource/HPACharts.tsx +237 -0
- package/src/components/resource/PVCUsageBar.tsx +59 -0
- package/src/components/resource/PrometheusCharts.tsx +160 -584
- package/src/components/resource/PrometheusChartsGrid.tsx +270 -0
- package/src/components/resource/RestartChart.tsx +133 -0
- package/src/components/resource/RightsizingStrip.test.ts +109 -0
- package/src/components/resource/RightsizingStrip.tsx +363 -0
- package/src/components/resource-drawer/ResourceDrawer.tsx +3 -1
- package/src/components/resources/CompositeRenderer.tsx +101 -0
- package/src/components/resources/ImageFilesystemModal.tsx +19 -12
- package/src/components/resources/PodFilesystemModal.tsx +6 -5
- package/src/components/resources/ResourceDetailDrawer.tsx +13 -3
- package/src/components/resources/ResourcesView.tsx +194 -17
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +1 -0
- package/src/components/resources/renderers/HPARenderer.tsx +20 -1
- package/src/components/resources/renderers/NamespaceRenderer.tsx +31 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +10 -4
- package/src/components/resources/renderers/PVCRenderer.tsx +19 -1
- package/src/components/resources/renderers/PodRenderer.tsx +30 -6
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +45 -1
- package/src/components/resources/renderers/RoleRenderer.tsx +27 -1
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +28 -1
- package/src/components/resources/renderers/ServiceRenderer.tsx +81 -8
- package/src/components/resources/renderers/WorkloadRenderer.tsx +51 -4
- package/src/components/resources/renderers/index.ts +2 -0
- package/src/components/resources/resource-utils.ts +2 -1
- package/src/components/rightsizing/RightsizingScanView.tsx +938 -0
- package/src/components/rightsizing/copy.test.ts +56 -0
- package/src/components/rightsizing/model.test.ts +227 -0
- package/src/components/rightsizing/model.ts +158 -0
- package/src/components/rightsizing/presentation.test.ts +104 -0
- package/src/components/rightsizing/presentation.ts +94 -0
- package/src/components/settings/MyPermissionsDialog.tsx +241 -0
- package/src/components/settings/SettingsDialog.tsx +1505 -165
- package/src/components/shared/CreateResourceDialog.tsx +9 -2
- package/src/components/shared/LargeClusterNamespacePicker.tsx +3 -3
- package/src/components/timeline/LocalTimelineScrubber.tsx +212 -0
- package/src/components/timeline/RetainedTimelineScrubber.tsx +311 -0
- package/src/components/timeline/TimelineList.tsx +86 -13
- package/src/components/timeline/TimelineSwimlanes.tsx +9 -1299
- package/src/components/timeline/TimelineView.tsx +873 -24
- package/src/components/timeline/TimelineView.urlparams.test.ts +335 -0
- package/src/components/traffic/TrafficFilterSidebar.tsx +10 -45
- package/src/components/traffic/TrafficFlowList.tsx +29 -15
- package/src/components/traffic/TrafficGraph.tsx +42 -24
- package/src/components/traffic/TrafficView.tsx +32 -19
- package/src/components/ui/CommandPalette.tsx +8 -215
- package/src/components/ui/DiagnosticsOverlay.tsx +219 -9
- package/src/components/ui/Markdown.tsx +3 -3
- package/src/components/ui/Omnibar.tsx +602 -0
- package/src/components/ui/RadarOmnibar.tsx +52 -0
- package/src/components/ui/SearchSyntaxHelp.tsx +89 -0
- package/src/components/ui/ShortcutHelpOverlay.tsx +3 -2
- package/src/components/ui/UpdateNotification.tsx +48 -36
- package/src/components/ui/command-items.ts +178 -0
- package/src/components/workload/WorkloadView.tsx +1342 -158
- package/src/context/ConnectionContext.tsx +146 -21
- package/src/context/DiagnoseCustomization.tsx +93 -0
- package/src/context/NavCustomization.tsx +75 -0
- package/src/context/TimelineSource.tsx +50 -0
- package/src/contexts/CapabilitiesContext.tsx +32 -8
- package/src/filter/FilterLocationBridge.tsx +30 -0
- package/src/hooks/useClusterLoadState.ts +73 -0
- package/src/hooks/useDocumentTitle.ts +25 -0
- package/src/hooks/useEventSource.ts +6 -0
- package/src/hooks/useKeyboardShortcuts.tsx +1 -0
- package/src/hooks/useMediaQuery.ts +21 -0
- package/src/hooks/useNavRailPinned.ts +46 -0
- package/src/hooks/useRecentResources.ts +49 -0
- package/src/index.css +162 -1
- package/src/index.ts +73 -1
- package/src/main.tsx +7 -5
- package/src/types/clusterLoadState.ts +33 -0
- package/src/types.ts +2 -0
- package/src/utils/auditBadges.ts +53 -0
- package/src/utils/navigation.ts +64 -1
- package/src/components/ui/NamespaceSelector.tsx +0 -436
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { useState, useMemo, useRef, useCallback } from 'react'
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
3
|
import { Loader2, TrendingUp } from 'lucide-react'
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
type CostTimeRange,
|
|
7
|
-
type OpenCostTrendSeries,
|
|
8
|
-
} from '../../api/client'
|
|
4
|
+
import { useOpenCostTrend, type CostTimeRange, type OpenCostTrendSeries } from '../../api/client'
|
|
5
|
+
import { formatCostAxis, formatCostPerHour } from './format'
|
|
9
6
|
|
|
10
7
|
const SERIES_COLORS = [
|
|
11
8
|
'#3b82f6', // blue-500
|
|
@@ -34,7 +31,7 @@ export function CostTrendChart() {
|
|
|
34
31
|
<div className="rounded-lg border border-theme-border bg-theme-surface/50 p-4">
|
|
35
32
|
<div className="flex items-center justify-center h-[200px] text-theme-text-tertiary">
|
|
36
33
|
<Loader2 className="w-5 h-5 animate-spin mr-2" />
|
|
37
|
-
Loading cost trend
|
|
34
|
+
Loading cost trend…
|
|
38
35
|
</div>
|
|
39
36
|
</div>
|
|
40
37
|
)
|
|
@@ -49,24 +46,12 @@ export function CostTrendChart() {
|
|
|
49
46
|
<div className="flex items-center justify-between px-4 py-2.5 border-b border-theme-border">
|
|
50
47
|
<div className="flex items-center gap-2">
|
|
51
48
|
<TrendingUp className="w-4 h-4 text-theme-text-tertiary" />
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<button
|
|
57
|
-
key={tr.value}
|
|
58
|
-
onClick={() => setTimeRange(tr.value)}
|
|
59
|
-
className={clsx(
|
|
60
|
-
'px-2 py-1 text-xs rounded-md transition-colors',
|
|
61
|
-
timeRange === tr.value
|
|
62
|
-
? 'bg-skyhook-600/20 text-blue-400 font-medium'
|
|
63
|
-
: 'text-theme-text-quaternary hover:text-theme-text-tertiary'
|
|
64
|
-
)}
|
|
65
|
-
>
|
|
66
|
-
{tr.label}
|
|
67
|
-
</button>
|
|
68
|
-
))}
|
|
49
|
+
<div>
|
|
50
|
+
<div className="text-xs font-medium text-theme-text-secondary">Cost rate trend</div>
|
|
51
|
+
<div className="text-[10px] text-theme-text-tertiary">Historical OpenCost allocation rate ($/hr)</div>
|
|
52
|
+
</div>
|
|
69
53
|
</div>
|
|
54
|
+
<CostTimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
|
70
55
|
</div>
|
|
71
56
|
<div className="p-4">
|
|
72
57
|
<StackedAreaChart series={data.series} />
|
|
@@ -76,7 +61,7 @@ export function CostTrendChart() {
|
|
|
76
61
|
)
|
|
77
62
|
}
|
|
78
63
|
|
|
79
|
-
function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
64
|
+
export function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
80
65
|
const svgRef = useRef<SVGSVGElement>(null)
|
|
81
66
|
const [hoverX, setHoverX] = useState<number | null>(null)
|
|
82
67
|
|
|
@@ -106,7 +91,7 @@ function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
|
106
91
|
const minTs = timestamps[0]
|
|
107
92
|
const maxTs = timestamps[timestamps.length - 1]
|
|
108
93
|
|
|
109
|
-
const seriesLookups = series.map(s => {
|
|
94
|
+
const seriesLookups = series.map((s) => {
|
|
110
95
|
const map = new Map<number, number>()
|
|
111
96
|
for (const dp of s.dataPoints) {
|
|
112
97
|
map.set(dp.timestamp, dp.value)
|
|
@@ -150,21 +135,47 @@ function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
|
150
135
|
|
|
151
136
|
// Build stacked area paths
|
|
152
137
|
const paths = series.map((_, si) => {
|
|
153
|
-
const topPoints = timestamps.map((ts, ti) => ({
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
138
|
+
const topPoints = timestamps.map((ts, ti) => ({
|
|
139
|
+
x: toX(ts),
|
|
140
|
+
y: toY(stacked[si][ti]),
|
|
141
|
+
}))
|
|
142
|
+
const bottomPoints =
|
|
143
|
+
si > 0
|
|
144
|
+
? timestamps.map((ts, ti) => ({
|
|
145
|
+
x: toX(ts),
|
|
146
|
+
y: toY(stacked[si - 1][ti]),
|
|
147
|
+
}))
|
|
148
|
+
: timestamps.map((ts) => ({ x: toX(ts), y: toY(0) }))
|
|
157
149
|
|
|
158
150
|
const topPath = topPoints.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`).join(' ')
|
|
159
|
-
const bottomPath = [...bottomPoints]
|
|
151
|
+
const bottomPath = [...bottomPoints]
|
|
152
|
+
.reverse()
|
|
153
|
+
.map((p, i) => `${i === 0 ? 'L' : 'L'}${p.x},${p.y}`)
|
|
154
|
+
.join(' ')
|
|
160
155
|
const areaPath = topPath + ' ' + bottomPath + ' Z'
|
|
161
156
|
const linePath = topPoints.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`).join(' ')
|
|
162
157
|
|
|
163
|
-
return {
|
|
158
|
+
return {
|
|
159
|
+
areaPath,
|
|
160
|
+
linePath,
|
|
161
|
+
color: SERIES_COLORS[si % SERIES_COLORS.length],
|
|
162
|
+
}
|
|
164
163
|
})
|
|
165
164
|
|
|
166
|
-
return {
|
|
167
|
-
|
|
165
|
+
return {
|
|
166
|
+
timestamps,
|
|
167
|
+
stacked,
|
|
168
|
+
minTs,
|
|
169
|
+
maxTs,
|
|
170
|
+
yMax,
|
|
171
|
+
seriesLookups,
|
|
172
|
+
toX,
|
|
173
|
+
toY,
|
|
174
|
+
yTicks,
|
|
175
|
+
xTicks,
|
|
176
|
+
paths,
|
|
177
|
+
}
|
|
178
|
+
}, [series, plotHeight, plotWidth])
|
|
168
179
|
|
|
169
180
|
// Hover data — depends on hoverX + chartData, must be a separate hook (called unconditionally)
|
|
170
181
|
const hoverData = useMemo(() => {
|
|
@@ -189,11 +200,15 @@ function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
|
189
200
|
const points = series.map((s, si) => {
|
|
190
201
|
const val = seriesLookups[si].get(closestTs) ?? 0
|
|
191
202
|
total += val
|
|
192
|
-
return {
|
|
203
|
+
return {
|
|
204
|
+
namespace: s.namespace,
|
|
205
|
+
value: val,
|
|
206
|
+
color: SERIES_COLORS[si % SERIES_COLORS.length],
|
|
207
|
+
}
|
|
193
208
|
})
|
|
194
209
|
|
|
195
210
|
return { ts: closestTs, x: toX(closestTs), total, points }
|
|
196
|
-
}, [hoverX, chartData, series])
|
|
211
|
+
}, [hoverX, chartData, series, plotWidth])
|
|
197
212
|
|
|
198
213
|
const handleMouseMove = useCallback((e: React.MouseEvent<SVGRectElement>) => {
|
|
199
214
|
const svg = svgRef.current
|
|
@@ -210,18 +225,15 @@ function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
|
210
225
|
|
|
211
226
|
return (
|
|
212
227
|
<div className="relative">
|
|
213
|
-
<svg
|
|
214
|
-
ref={svgRef}
|
|
215
|
-
viewBox={`0 0 ${width} ${height}`}
|
|
216
|
-
className="w-full"
|
|
217
|
-
preserveAspectRatio="xMidYMid meet"
|
|
218
|
-
>
|
|
228
|
+
<svg ref={svgRef} viewBox={`0 0 ${width} ${height}`} className="w-full" preserveAspectRatio="xMidYMid meet">
|
|
219
229
|
{/* Grid lines */}
|
|
220
230
|
{yTicks.map((tick, i) => (
|
|
221
231
|
<line
|
|
222
232
|
key={`grid-${i}`}
|
|
223
|
-
x1={marginLeft}
|
|
224
|
-
|
|
233
|
+
x1={marginLeft}
|
|
234
|
+
y1={tick.y}
|
|
235
|
+
x2={width - marginRight}
|
|
236
|
+
y2={tick.y}
|
|
225
237
|
stroke="currentColor"
|
|
226
238
|
className="text-theme-border/30"
|
|
227
239
|
strokeWidth="1"
|
|
@@ -250,7 +262,7 @@ function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
|
250
262
|
key={`xlabel-${i}`}
|
|
251
263
|
x={tick.x}
|
|
252
264
|
y={height - 4}
|
|
253
|
-
textAnchor=
|
|
265
|
+
textAnchor={i === 0 ? 'start' : i === xTicks.length - 1 ? 'end' : 'middle'}
|
|
254
266
|
className="fill-theme-text-secondary"
|
|
255
267
|
fontSize="10"
|
|
256
268
|
fontFamily="ui-monospace, monospace"
|
|
@@ -261,11 +273,7 @@ function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
|
261
273
|
|
|
262
274
|
{/* Stacked area fills (render bottom to top) */}
|
|
263
275
|
{paths.map((p, i) => (
|
|
264
|
-
<path
|
|
265
|
-
key={`area-${i}`}
|
|
266
|
-
d={p.areaPath}
|
|
267
|
-
fill={p.color + '33'}
|
|
268
|
-
/>
|
|
276
|
+
<path key={`area-${i}`} d={p.areaPath} fill={p.color + '33'} />
|
|
269
277
|
))}
|
|
270
278
|
|
|
271
279
|
{/* Lines (top edges of each area) */}
|
|
@@ -283,8 +291,10 @@ function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
|
283
291
|
{/* Hover crosshair */}
|
|
284
292
|
{hoverData && (
|
|
285
293
|
<line
|
|
286
|
-
x1={hoverData.x}
|
|
287
|
-
|
|
294
|
+
x1={hoverData.x}
|
|
295
|
+
y1={marginTop}
|
|
296
|
+
x2={hoverData.x}
|
|
297
|
+
y2={marginTop + plotHeight}
|
|
288
298
|
stroke="currentColor"
|
|
289
299
|
className="text-theme-text-tertiary"
|
|
290
300
|
strokeWidth="1"
|
|
@@ -294,8 +304,10 @@ function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
|
294
304
|
|
|
295
305
|
{/* Mouse event overlay */}
|
|
296
306
|
<rect
|
|
297
|
-
x={marginLeft}
|
|
298
|
-
|
|
307
|
+
x={marginLeft}
|
|
308
|
+
y={marginTop}
|
|
309
|
+
width={plotWidth}
|
|
310
|
+
height={plotHeight}
|
|
299
311
|
fill="transparent"
|
|
300
312
|
style={{ cursor: 'crosshair' }}
|
|
301
313
|
onMouseMove={handleMouseMove}
|
|
@@ -315,28 +327,30 @@ function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
|
315
327
|
<div className="bg-theme-surface border border-theme-border rounded-lg shadow-lg px-3 py-2 text-xs whitespace-nowrap">
|
|
316
328
|
<div className="text-theme-text-tertiary mb-1.5 font-mono">
|
|
317
329
|
{new Date(hoverData.ts * 1000).toLocaleString([], {
|
|
318
|
-
month: 'short',
|
|
330
|
+
month: 'short',
|
|
331
|
+
day: 'numeric',
|
|
332
|
+
hour: '2-digit',
|
|
333
|
+
minute: '2-digit',
|
|
319
334
|
})}
|
|
320
335
|
</div>
|
|
321
336
|
{hoverData.points
|
|
322
|
-
.filter(p => p.value > 0)
|
|
337
|
+
.filter((p) => p.value > 0)
|
|
323
338
|
.sort((a, b) => b.value - a.value)
|
|
324
339
|
.map((p, i) => (
|
|
325
340
|
<div key={i} className="flex items-center gap-2 py-0.5">
|
|
326
|
-
<div
|
|
327
|
-
className="w-2 h-2 rounded-full shrink-0"
|
|
328
|
-
style={{ backgroundColor: p.color }}
|
|
329
|
-
/>
|
|
341
|
+
<div className="w-2 h-2 rounded-full shrink-0" style={{ backgroundColor: p.color }} />
|
|
330
342
|
<span className="text-theme-text-secondary">{p.namespace}</span>
|
|
331
343
|
<span className="text-theme-text-primary font-semibold ml-auto pl-3 tabular-nums">
|
|
332
344
|
{formatCostTooltip(p.value)}
|
|
333
345
|
</span>
|
|
334
346
|
</div>
|
|
335
347
|
))}
|
|
336
|
-
|
|
337
|
-
<
|
|
338
|
-
|
|
339
|
-
|
|
348
|
+
{series.length > 1 && (
|
|
349
|
+
<div className="border-t border-theme-border/50 mt-1 pt-1 flex justify-between text-theme-text-primary font-semibold">
|
|
350
|
+
<span>Total</span>
|
|
351
|
+
<span className="tabular-nums">{formatCostTooltip(hoverData.total)}</span>
|
|
352
|
+
</div>
|
|
353
|
+
)}
|
|
340
354
|
</div>
|
|
341
355
|
</div>
|
|
342
356
|
)}
|
|
@@ -344,7 +358,7 @@ function StackedAreaChart({ series }: { series: OpenCostTrendSeries[] }) {
|
|
|
344
358
|
)
|
|
345
359
|
}
|
|
346
360
|
|
|
347
|
-
function ChartLegend({ series }: { series: OpenCostTrendSeries[] }) {
|
|
361
|
+
export function ChartLegend({ series }: { series: OpenCostTrendSeries[] }) {
|
|
348
362
|
return (
|
|
349
363
|
<div className="flex flex-wrap gap-x-4 gap-y-1 mt-2">
|
|
350
364
|
{series.map((s, i) => (
|
|
@@ -360,20 +374,37 @@ function ChartLegend({ series }: { series: OpenCostTrendSeries[] }) {
|
|
|
360
374
|
)
|
|
361
375
|
}
|
|
362
376
|
|
|
363
|
-
function
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
377
|
+
export function CostTimeRangeSelector({
|
|
378
|
+
value,
|
|
379
|
+
onChange,
|
|
380
|
+
}: {
|
|
381
|
+
value: CostTimeRange
|
|
382
|
+
onChange: (value: CostTimeRange) => void
|
|
383
|
+
}) {
|
|
384
|
+
return (
|
|
385
|
+
<div className="flex items-center gap-1">
|
|
386
|
+
{TIME_RANGES.map((range) => (
|
|
387
|
+
<button
|
|
388
|
+
key={range.value}
|
|
389
|
+
type="button"
|
|
390
|
+
aria-pressed={value === range.value}
|
|
391
|
+
onClick={() => onChange(range.value)}
|
|
392
|
+
className={clsx(
|
|
393
|
+
'rounded-md px-2 py-1 text-xs transition-colors',
|
|
394
|
+
value === range.value
|
|
395
|
+
? 'bg-accent-muted font-medium text-accent-text'
|
|
396
|
+
: 'text-theme-text-tertiary hover:bg-theme-hover hover:text-theme-text-primary',
|
|
397
|
+
)}
|
|
398
|
+
>
|
|
399
|
+
{range.label}
|
|
400
|
+
</button>
|
|
401
|
+
))}
|
|
402
|
+
</div>
|
|
403
|
+
)
|
|
369
404
|
}
|
|
370
405
|
|
|
371
406
|
function formatCostTooltip(value: number): string {
|
|
372
|
-
|
|
373
|
-
if (value >= 1) return `$${value.toFixed(2)}/hr`
|
|
374
|
-
if (value >= 0.01) return `$${value.toFixed(3)}/hr`
|
|
375
|
-
if (value > 0) return `$${value.toFixed(4)}/hr`
|
|
376
|
-
return '$0.00/hr'
|
|
407
|
+
return formatCostPerHour(value)
|
|
377
408
|
}
|
|
378
409
|
|
|
379
410
|
function formatTimestamp(unix: number): string {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { resourceKindForCostWorkload } from './CostView'
|
|
3
|
+
|
|
4
|
+
describe('resourceKindForCostWorkload', () => {
|
|
5
|
+
it('does not link standalone aggregate rows as pods', () => {
|
|
6
|
+
expect(resourceKindForCostWorkload('standalone')).toBeNull()
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('keeps static pod rows linkable to their mirror pod', () => {
|
|
10
|
+
expect(resourceKindForCostWorkload('staticpod')).toBe('Pod')
|
|
11
|
+
})
|
|
12
|
+
})
|