@skyhook-io/k8s-ui 1.7.7 → 1.7.9

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.
Files changed (48) hide show
  1. package/package.json +6 -1
  2. package/src/components/charts/PrometheusChartsView.tsx +233 -0
  3. package/src/components/charts/index.ts +13 -0
  4. package/src/components/checks/ChecksView.tsx +5 -1
  5. package/src/components/gitops/GitOpsTableView.tsx +1 -2
  6. package/src/components/gitops/insights/GitOpsInsightViews.tsx +3 -3
  7. package/src/components/issues/IssuesView.tsx +374 -0
  8. package/src/components/issues/index.ts +24 -0
  9. package/src/components/issues/issues.test.ts +100 -0
  10. package/src/components/issues/severity.ts +125 -0
  11. package/src/components/issues/types.ts +194 -0
  12. package/src/components/resources/ResourcesView.tsx +2 -19
  13. package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +1 -1
  14. package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +1 -1
  15. package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +1 -1
  16. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +1 -1
  17. package/src/components/resources/renderers/CompositionRenderer.tsx +3 -3
  18. package/src/components/resources/renderers/CrossplanePackageRenderer.tsx +5 -5
  19. package/src/components/resources/renderers/CrossplaneProviderConfigRenderer.tsx +1 -1
  20. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +1 -1
  21. package/src/components/resources/renderers/PodRenderer.test.tsx +53 -0
  22. package/src/components/resources/renderers/PodRenderer.tsx +7 -1
  23. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +1 -1
  24. package/src/components/resources/renderers/WorkloadRenderer.test.tsx +41 -0
  25. package/src/components/resources/renderers/WorkloadRenderer.tsx +49 -7
  26. package/src/components/resources/renderers/XRDRenderer.tsx +2 -2
  27. package/src/components/shared/DetailShell.tsx +107 -0
  28. package/src/components/shared/ManagedByChip.tsx +149 -16
  29. package/src/components/shared/ResourceRendererDispatch.test.tsx +45 -0
  30. package/src/components/shared/ResourceRendererDispatch.tsx +13 -1
  31. package/src/components/shared/index.ts +2 -1
  32. package/src/components/timeline/TimelineSwimlanes.tsx +1320 -0
  33. package/src/components/timeline/index.ts +1 -0
  34. package/src/components/topology/K8sResourceNode.tsx +60 -60
  35. package/src/components/topology/TopologyFilterSidebar.tsx +25 -3
  36. package/src/components/topology/TopologyGraph.tsx +168 -52
  37. package/src/components/topology/TopologySearch.tsx +17 -8
  38. package/src/components/topology/topology-search-match.test.ts +1 -1
  39. package/src/components/topology/topology.css +18 -0
  40. package/src/components/ui/Tooltip.tsx +37 -2
  41. package/src/components/workload/WorkloadView.tsx +118 -112
  42. package/src/index.ts +5 -0
  43. package/src/theme/components.css +35 -0
  44. package/src/types/core.ts +3 -2
  45. package/src/utils/env-from.ts +3 -0
  46. package/src/utils/index.ts +1 -0
  47. package/src/utils/replica-scalers.ts +9 -0
  48. package/src/components/resources/resources-search-sidebar-hint.test.ts +0 -85
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyhook-io/k8s-ui",
3
- "version": "1.7.7",
3
+ "version": "1.7.9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/skyhook-io/radar",
@@ -31,6 +31,11 @@
31
31
  "./src/components/gitops/*.tsx",
32
32
  "./src/components/gitops/*.ts"
33
33
  ],
34
+ "./components/issues": "./src/components/issues/index.ts",
35
+ "./components/issues/*": [
36
+ "./src/components/issues/*.tsx",
37
+ "./src/components/issues/*.ts"
38
+ ],
34
39
  "./components/timeline/*": [
35
40
  "./src/components/timeline/*.tsx",
36
41
  "./src/components/timeline/*.ts"
@@ -0,0 +1,233 @@
1
+ import { clsx } from 'clsx'
2
+ import { BarChart3, Wifi, WifiOff, Loader2 } from 'lucide-react'
3
+ import { AreaChart } from './AreaChart'
4
+ import { MetricsSummary } from './MetricsSummary'
5
+ import { SeriesLegend } from './SeriesLegend'
6
+ import type { TimeSeries, ReferenceLine } from './types'
7
+
8
+ // Pure presentational Prometheus charts view, shared between radar's WorkloadView
9
+ // and Radar Hub's Application metrics tab. It owns NO data fetching — the host
10
+ // passes status, the selected category/range, the metrics result, and an
11
+ // onConnect callback. radar/web injects its global-apiBase hooks; the hub injects
12
+ // per-cluster tunnel fetches. Built on the AreaChart/MetricsSummary/SeriesLegend
13
+ // primitives already in this package.
14
+
15
+ export type PrometheusMetricCategory = 'cpu' | 'memory' | 'network_rx' | 'network_tx' | 'filesystem' | 'restarts'
16
+ export type PrometheusTimeRange = '10m' | '30m' | '1h' | '3h' | '6h' | '12h' | '24h' | '48h' | '7d' | '14d'
17
+
18
+ export interface MetricCategoryDef {
19
+ key: PrometheusMetricCategory
20
+ label: string
21
+ color: string // tailwind text class for the summary's current value
22
+ chartColor: string // hex for the SVG line
23
+ fillColor: string // hex + alpha for the SVG fill
24
+ }
25
+
26
+ export const WORKLOAD_METRIC_CATEGORIES: MetricCategoryDef[] = [
27
+ { key: 'cpu', label: 'CPU', color: 'text-blue-400', chartColor: '#60a5fa', fillColor: '#60a5fa22' },
28
+ { key: 'memory', label: 'Memory', color: 'text-purple-400', chartColor: '#c084fc', fillColor: '#c084fc22' },
29
+ { key: 'network_rx', label: 'Net RX', color: 'text-emerald-400', chartColor: '#34d399', fillColor: '#34d39922' },
30
+ { key: 'network_tx', label: 'Net TX', color: 'text-orange-400', chartColor: '#fb923c', fillColor: '#fb923c22' },
31
+ { key: 'filesystem', label: 'Disk I/O', color: 'text-amber-400', chartColor: '#fbbf24', fillColor: '#fbbf2422' },
32
+ ]
33
+
34
+ export const NODE_METRIC_CATEGORIES: MetricCategoryDef[] = [
35
+ { key: 'cpu', label: 'CPU', color: 'text-blue-400', chartColor: '#60a5fa', fillColor: '#60a5fa22' },
36
+ { key: 'memory', label: 'Memory', color: 'text-purple-400', chartColor: '#c084fc', fillColor: '#c084fc22' },
37
+ { key: 'filesystem', label: 'Disk', color: 'text-amber-400', chartColor: '#fbbf24', fillColor: '#fbbf2422' },
38
+ ]
39
+
40
+ export const METRIC_TIME_RANGES: { value: PrometheusTimeRange; label: string }[] = [
41
+ { value: '10m', label: '10m' },
42
+ { value: '30m', label: '30m' },
43
+ { value: '1h', label: '1h' },
44
+ { value: '3h', label: '3h' },
45
+ { value: '6h', label: '6h' },
46
+ { value: '12h', label: '12h' },
47
+ { value: '24h', label: '24h' },
48
+ { value: '7d', label: '7d' },
49
+ ]
50
+
51
+ const SUPPORTED_KINDS = new Set([
52
+ 'Pod', 'Deployment', 'StatefulSet', 'DaemonSet', 'ReplicaSet', 'Job', 'CronJob', 'Node',
53
+ ])
54
+
55
+ export interface PrometheusResourceMetricsResult {
56
+ unit: string
57
+ result?: { series?: TimeSeries[] }
58
+ query?: string
59
+ hint?: string
60
+ }
61
+
62
+ export interface PrometheusChartsViewProps {
63
+ kind: string
64
+ /** When false (embedded in an Overview), hide entirely when not connected or no data. */
65
+ showEmptyState?: boolean
66
+ // Connection status (host-provided)
67
+ statusLoading: boolean
68
+ isConnected: boolean
69
+ statusError?: string
70
+ onConnect: () => void
71
+ connecting: boolean
72
+ // Selection (controlled by host)
73
+ category: PrometheusMetricCategory
74
+ onCategoryChange: (c: PrometheusMetricCategory) => void
75
+ range: PrometheusTimeRange
76
+ onRangeChange: (r: PrometheusTimeRange) => void
77
+ // Metrics (host-fetched)
78
+ metrics?: PrometheusResourceMetricsResult
79
+ metricsLoading: boolean
80
+ metricsError?: Error | null
81
+ referenceLines?: ReferenceLine[]
82
+ }
83
+
84
+ export function PrometheusChartsView({
85
+ kind,
86
+ showEmptyState = false,
87
+ statusLoading,
88
+ isConnected,
89
+ statusError,
90
+ onConnect,
91
+ connecting,
92
+ category,
93
+ onCategoryChange,
94
+ range,
95
+ onRangeChange,
96
+ metrics,
97
+ metricsLoading,
98
+ metricsError,
99
+ referenceLines,
100
+ }: PrometheusChartsViewProps) {
101
+ const categories = kind === 'Node' ? NODE_METRIC_CATEGORIES : WORKLOAD_METRIC_CATEGORIES
102
+ const isSupported = SUPPORTED_KINDS.has(kind)
103
+ const activeCategoryDef = categories.find((c) => c.key === category) || categories[0]
104
+ const series = metrics?.result?.series ?? []
105
+
106
+ if (!isSupported) return null
107
+
108
+ if (statusLoading) {
109
+ if (!showEmptyState) return null
110
+ return (
111
+ <div className="flex items-center justify-center py-12 text-theme-text-tertiary">
112
+ <Loader2 className="mr-2 h-5 w-5 animate-spin" />
113
+ Checking Prometheus availability...
114
+ </div>
115
+ )
116
+ }
117
+
118
+ if (!showEmptyState) {
119
+ if (!isConnected) return null
120
+ if (!metricsLoading && !metricsError && !series.length) return null
121
+ }
122
+
123
+ if (!isConnected) {
124
+ return (
125
+ <div className="flex flex-col items-center justify-center gap-4 py-12">
126
+ <WifiOff className="h-10 w-10 text-theme-text-tertiary" />
127
+ <div className="text-center">
128
+ <p className="mb-1 text-sm text-theme-text-secondary">Prometheus not connected</p>
129
+ <p className="mb-4 text-xs text-theme-text-tertiary">
130
+ {statusError || 'Connect to view historical CPU, memory, and network metrics'}
131
+ </p>
132
+ <button
133
+ type="button"
134
+ onClick={onConnect}
135
+ disabled={connecting}
136
+ className="btn-brand inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium"
137
+ >
138
+ {connecting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Wifi className="h-4 w-4" />}
139
+ Discover Prometheus
140
+ </button>
141
+ </div>
142
+ </div>
143
+ )
144
+ }
145
+
146
+ return (
147
+ <div className="flex h-full flex-col">
148
+ {/* Toolbar */}
149
+ <div className="flex shrink-0 items-center justify-between border-b border-theme-border bg-theme-surface/50 px-4 py-2.5">
150
+ <div className="flex items-center gap-1">
151
+ <BarChart3 className="mr-2 h-4 w-4 text-theme-text-tertiary" />
152
+ {categories.map((cat) => (
153
+ <button
154
+ key={cat.key}
155
+ type="button"
156
+ onClick={() => onCategoryChange(cat.key)}
157
+ className={clsx(
158
+ 'rounded-md px-2.5 py-1 text-xs font-medium transition-colors',
159
+ category === cat.key
160
+ ? 'bg-theme-elevated text-theme-text-primary shadow-sm'
161
+ : 'text-theme-text-tertiary hover:bg-theme-elevated/50 hover:text-theme-text-secondary',
162
+ )}
163
+ >
164
+ {cat.label}
165
+ </button>
166
+ ))}
167
+ </div>
168
+ <select
169
+ value={range}
170
+ onChange={(e) => onRangeChange(e.target.value as PrometheusTimeRange)}
171
+ className="rounded-md border border-theme-border bg-theme-elevated px-2 py-1 text-xs text-theme-text-secondary focus:outline-none focus:ring-1 focus:ring-blue-500/50"
172
+ >
173
+ {METRIC_TIME_RANGES.map((tr) => (
174
+ <option key={tr.value} value={tr.value}>
175
+ {tr.label}
176
+ </option>
177
+ ))}
178
+ </select>
179
+ </div>
180
+
181
+ {/* Chart area */}
182
+ <div className="min-h-[280px] flex-1 p-4">
183
+ {metricsLoading ? (
184
+ <div className="flex min-h-[240px] items-center justify-center text-theme-text-tertiary">
185
+ <Loader2 className="mr-2 h-5 w-5 animate-spin" />
186
+ Loading metrics...
187
+ </div>
188
+ ) : metricsError ? (
189
+ <div className="flex h-full items-center justify-center text-sm text-red-400">
190
+ Failed to load metrics: {(metricsError as Error).message}
191
+ </div>
192
+ ) : series.length ? (
193
+ <div className="flex h-full flex-col gap-4">
194
+ <MetricsSummary series={series} unit={metrics!.unit} currentColorClass={activeCategoryDef.color} />
195
+ <div className="min-h-0 flex-1">
196
+ <AreaChart
197
+ series={series}
198
+ color={activeCategoryDef.chartColor}
199
+ fillColor={activeCategoryDef.fillColor}
200
+ unit={metrics!.unit}
201
+ referenceLines={referenceLines}
202
+ />
203
+ </div>
204
+ {series.length > 1 && <SeriesLegend series={series} color={activeCategoryDef.chartColor} />}
205
+ </div>
206
+ ) : (
207
+ <div className="flex h-full flex-col items-center justify-center text-theme-text-tertiary">
208
+ <BarChart3 className="mb-2 h-8 w-8 opacity-40" />
209
+ <p className="text-sm">No data for this time range</p>
210
+ <p className="mt-1 text-xs text-theme-text-quaternary">
211
+ Try a different time range or check that metrics are being collected
212
+ </p>
213
+ {metrics?.hint && (
214
+ <p className="mt-3 w-full max-w-lg rounded border border-yellow-500/30 bg-yellow-500/10 px-3 py-2 text-xs text-yellow-700 dark:text-yellow-400">
215
+ {metrics.hint}
216
+ </p>
217
+ )}
218
+ {metrics?.query && (
219
+ <details className="mt-3 w-full max-w-lg text-left">
220
+ <summary className="cursor-pointer text-xs text-theme-text-quaternary hover:text-theme-text-tertiary">
221
+ Diagnostics: show PromQL query
222
+ </summary>
223
+ <div className="mt-2 break-all rounded border border-theme-border bg-theme-base p-2 font-mono text-xs text-theme-text-secondary">
224
+ {metrics.query}
225
+ </div>
226
+ </details>
227
+ )}
228
+ </div>
229
+ )}
230
+ </div>
231
+ </div>
232
+ )
233
+ }
@@ -1,6 +1,19 @@
1
1
  export { AreaChart } from './AreaChart'
2
2
  export { MetricsSummary } from './MetricsSummary'
3
3
  export { SeriesLegend } from './SeriesLegend'
4
+ export {
5
+ PrometheusChartsView,
6
+ WORKLOAD_METRIC_CATEGORIES,
7
+ NODE_METRIC_CATEGORIES,
8
+ METRIC_TIME_RANGES,
9
+ } from './PrometheusChartsView'
10
+ export type {
11
+ PrometheusChartsViewProps,
12
+ PrometheusMetricCategory,
13
+ PrometheusTimeRange,
14
+ MetricCategoryDef,
15
+ PrometheusResourceMetricsResult,
16
+ } from './PrometheusChartsView'
4
17
  export { SERIES_COLORS, seriesColor, seriesFill, computeShortLabels } from './colors'
5
18
  export { formatMetricValue, formatTimestamp } from './format'
6
19
  export { computeSaturation } from './saturation'
@@ -703,7 +703,11 @@ function FindingLine({
703
703
  {body}
704
704
  </button>
705
705
  ) : resourceHref ? (
706
- <a href={resourceHref(r)} className={cls}>
706
+ // Opens in a new tab to match the ExternalLink glyph in `body`. For the
707
+ // Hub fleet view this href crosses the Hub→Cluster SPA boundary (a full
708
+ // document nav); a new tab keeps the Checks queue intact and avoids the
709
+ // cross-tree browser-Back dead-end (SKY-931).
710
+ <a href={resourceHref(r)} target="_blank" rel="noreferrer" className={cls}>
707
711
  {body}
708
712
  </a>
709
713
  ) : (
@@ -552,7 +552,7 @@ export function GitOpsTableView({
552
552
  // understand why some destinations show as unmatched.
553
553
  <div className="shrink-0 border-b border-amber-500/30 bg-amber-500/10 px-4 py-2 text-xs text-amber-700 dark:text-amber-300">
554
554
  <span className="font-medium">Cross-cluster mapping limited for {forbiddenSecretsClusters.join(', ')}.</span>{' '}
555
- Owner needs <code>get secrets</code> in the <code>argocd</code> namespace there; destination
555
+ Owner needs <code className="inline-code">get secrets</code> in the <code className="inline-code">argocd</code> namespace there; destination
556
556
  correlation falls back to name-only resolution.
557
557
  </div>
558
558
  )}
@@ -1915,4 +1915,3 @@ function newestConditionTime(resource: any): string {
1915
1915
  .sort()
1916
1916
  return times[times.length - 1] ?? ''
1917
1917
  }
1918
-
@@ -818,12 +818,12 @@ export function GitOpsChangesView({ insight, error, onOpenResource, focusKey, tr
818
818
  <div className="border-b border-theme-border bg-theme-base/40 px-4 py-2 text-[11px] text-theme-text-tertiary">
819
819
  Radar reads each resource's drift status from the controller. For a line-by-line diff, {insight.summary.tool === 'fluxcd' ? (
820
820
  insight.summary.kind === 'HelmRelease' ? (
821
- <>run <code className="rounded bg-theme-elevated px-1 py-0.5 font-mono text-[10px]">helm diff upgrade {insight.summary.name} &lt;chart&gt;</code> (requires the helm-diff plugin).</>
821
+ <>run <code className="inline-code text-[10px]">helm diff upgrade {insight.summary.name} &lt;chart&gt;</code> (requires the helm-diff plugin).</>
822
822
  ) : (
823
- <>run <code className="rounded bg-theme-elevated px-1 py-0.5 font-mono text-[10px]">flux diff kustomization {insight.summary.name} --path &lt;local-manifests&gt;</code>.</>
823
+ <>run <code className="inline-code text-[10px]">flux diff kustomization {insight.summary.name} --path &lt;local-manifests&gt;</code>.</>
824
824
  )
825
825
  ) : (
826
- <>use the Argo CD UI or run <code className="rounded bg-theme-elevated px-1 py-0.5 font-mono text-[10px]">argocd app diff {insight.summary.name}</code>.</>
826
+ <>use the Argo CD UI or run <code className="inline-code text-[10px]">argocd app diff {insight.summary.name}</code>.</>
827
827
  )}
828
828
  </div>
829
829
  )}