@skyhook-io/radar-app 1.16.2 → 1.17.0

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 (93) hide show
  1. package/package.json +5 -5
  2. package/src/App.tsx +60 -12
  3. package/src/RadarApp.tsx +27 -17
  4. package/src/api/client.goneResource.test.ts +49 -0
  5. package/src/api/client.ts +168 -55
  6. package/src/api/cnpg.ts +95 -0
  7. package/src/api/diagnose.test.ts +40 -0
  8. package/src/api/diagnose.ts +12 -1
  9. package/src/api/radarFeatures.render.test.tsx +19 -1
  10. package/src/api/radarFeatures.ts +3 -0
  11. package/src/api/timelineSource.ts +1 -1
  12. package/src/api/traffic.sources.test.ts +55 -0
  13. package/src/api/traffic.ts +173 -14
  14. package/src/components/NoClusterAccessBanner.tsx +68 -0
  15. package/src/components/applications/ApplicationsView.tsx +3 -0
  16. package/src/components/capacity/CapacityPoolDetail.tsx +18 -7
  17. package/src/components/capacity/CapacityView.test.tsx +31 -1
  18. package/src/components/capacity/shared.tsx +12 -21
  19. package/src/components/cnpg/CNPGClusterActivity.tsx +49 -0
  20. package/src/components/cnpg/CNPGClusterLogs.tsx +62 -0
  21. package/src/components/cnpg/CNPGDeclarations.tsx +311 -0
  22. package/src/components/cnpg/CNPGDetailPage.tsx +228 -0
  23. package/src/components/cnpg/CNPGDrawerTrail.tsx +37 -0
  24. package/src/components/cnpg/CNPGOperator.tsx +234 -0
  25. package/src/components/cnpg/CNPGOverview.tsx +307 -0
  26. package/src/components/cnpg/CNPGPooling.tsx +116 -0
  27. package/src/components/cnpg/CNPGProtection.tsx +320 -0
  28. package/src/components/cnpg/CNPGSummaryHost.tsx +143 -0
  29. package/src/components/cnpg/CNPGView.tsx +156 -0
  30. package/src/components/cnpg/paths.ts +13 -0
  31. package/src/components/cnpg/routes.test.ts +60 -0
  32. package/src/components/cnpg/routes.ts +112 -0
  33. package/src/components/cnpg/shared.tsx +290 -0
  34. package/src/components/cnpg/useCNPGSidebarWorkspace.ts +88 -0
  35. package/src/components/diagnose/AgentSetupNotice.test.tsx +58 -0
  36. package/src/components/diagnose/AgentSetupNotice.tsx +142 -51
  37. package/src/components/diagnose/ApplyDialog.ack.test.tsx +47 -0
  38. package/src/components/diagnose/ApplyDialog.test.tsx +59 -6
  39. package/src/components/diagnose/ApplyDialog.tsx +26 -28
  40. package/src/components/diagnose/DiagnoseContext.test.ts +43 -0
  41. package/src/components/diagnose/DiagnoseContext.tsx +136 -54
  42. package/src/components/diagnose/DiagnoseSurface.tsx +16 -3
  43. package/src/components/diagnose/InvestigationView.tsx +39 -2
  44. package/src/components/diagnose/LocalDiagnoseAction.tsx +40 -20
  45. package/src/components/diagnose/agentCatalog.ts +2 -2
  46. package/src/components/diagnose/diagnoseEvidenceTypes.ts +2 -0
  47. package/src/components/dock/TrafficFlowListTab.tsx +27 -6
  48. package/src/components/execution/BatchExecutionView.tsx +11 -11
  49. package/src/components/helm/ChartBrowser.tsx +9 -10
  50. package/src/components/helm/HelmCompareRoute.tsx +108 -115
  51. package/src/components/helm/HelmReleaseDrawer.tsx +26 -44
  52. package/src/components/helm/InstallWizard.tsx +5 -2
  53. package/src/components/helm/RevisionHistory.tsx +3 -2
  54. package/src/components/helm/TrackChartSourceDialog.tsx +10 -2
  55. package/src/components/helm/ValuesViewer.tsx +1 -1
  56. package/src/components/home/ClusterHealthCard.tsx +7 -0
  57. package/src/components/home/mcpToolCatalog.ts +1 -1
  58. package/src/components/resources/ResourceDetailDrawer.tsx +6 -0
  59. package/src/components/resources/ResourcesView.tsx +6 -38
  60. package/src/components/resources/renderers/KyvernoPolicyQueued.tsx +2 -1
  61. package/src/components/resources/renderers/NodeRenderer.tsx +7 -1
  62. package/src/components/resources/renderers/ServiceRenderer.test.tsx +81 -0
  63. package/src/components/resources/renderers/ServiceRenderer.tsx +19 -14
  64. package/src/components/settings/SettingsDialog.tsx +122 -19
  65. package/src/components/shared/LargeClusterNamespacePicker.tsx +11 -5
  66. package/src/components/timeline/TimelineList.test.ts +47 -29
  67. package/src/components/timeline/TimelineList.tsx +60 -11
  68. package/src/components/timeline/TimelineRetentionNote.test.tsx +49 -0
  69. package/src/components/timeline/TimelineRetentionNote.tsx +20 -0
  70. package/src/components/timeline/TimelineView.tsx +16 -2
  71. package/src/components/timeline/TimelineView.urlparams.test.ts +6 -0
  72. package/src/components/traffic/TrafficConnectionsTable.tsx +144 -0
  73. package/src/components/traffic/TrafficFilterSidebar.tsx +22 -1
  74. package/src/components/traffic/TrafficFlowListContext.tsx +12 -4
  75. package/src/components/traffic/TrafficFocusSearch.tsx +172 -0
  76. package/src/components/traffic/TrafficGraph.tsx +109 -26
  77. package/src/components/traffic/TrafficGraphTooLarge.tsx +150 -0
  78. package/src/components/traffic/TrafficView.tsx +582 -149
  79. package/src/components/traffic/trafficFilters.test.ts +398 -1
  80. package/src/components/traffic/trafficFilters.ts +408 -0
  81. package/src/components/whats-new/WhatsNew.test.ts +107 -28
  82. package/src/components/whats-new/WhatsNew.tsx +146 -86
  83. package/src/components/whats-new/WhatsNewDialog.test.tsx +132 -10
  84. package/src/components/whats-new/releaseNotes.ts +155 -24
  85. package/src/components/workload/WorkloadView.test.ts +2 -1
  86. package/src/components/workload/WorkloadView.tsx +165 -294
  87. package/src/context/TimelineCustomization.tsx +15 -0
  88. package/src/contexts/CapabilitiesContext.tsx +9 -1
  89. package/src/hooks/useGitOpsWriteGuard.test.tsx +457 -0
  90. package/src/hooks/useGitOpsWriteGuard.ts +193 -0
  91. package/src/hooks/useResolvedGitOpsOwner.ts +256 -0
  92. package/src/hooks/useResourceCounts.ts +49 -0
  93. package/src/components/helm/RoleGatedPanel.tsx +0 -47
@@ -1,38 +1,94 @@
1
1
  import type { LucideIcon } from 'lucide-react'
2
- import { Bot, GitCompareArrows, Layers, Network, Scale } from 'lucide-react'
2
+ import { Bot, GitCompareArrows, Layers, Network, RefreshCcw, Scale, ScrollText, SlidersHorizontal } from 'lucide-react'
3
3
  import { compareVersions } from '../../utils/version'
4
4
 
5
5
  export interface ReleaseHighlight {
6
6
  id: string
7
7
  icon: LucideIcon
8
8
  title: string
9
+ /**
10
+ * Shown on a grid card, so at most 150 characters. Read next to other
11
+ * releases' highlights, so it must stand on its own.
12
+ */
9
13
  description: string
14
+ /**
15
+ * Fuller copy for when this highlight leads the dialog. Only a release's
16
+ * top highlight can ever lead (every other one scores at or below it), so
17
+ * only that one may set it.
18
+ */
19
+ leadDescription?: string
20
+ /**
21
+ * 1-10, comparable across releases: it decides which highlights a user who
22
+ * skipped releases sees, and whether the dialog opens by itself.
23
+ * 9-10 a new capability that defines the release
24
+ * 7-8 a solid feature a typical user would go try now
25
+ * 4-6 a useful improvement
26
+ * 1-3 polish
27
+ * Most highlights are not 9s. When every card is a 7, every release
28
+ * interrupts the user, which is what the score exists to prevent.
29
+ */
30
+ importance: number
10
31
  /** In-app route the highlight's call to action opens. */
11
32
  path?: string
12
33
  cta?: string
13
- /** Icon tile color, so the cards read apart at a glance. The lead card always uses the accent. */
14
- tone?: HighlightTone
15
34
  }
16
35
 
17
- export type HighlightTone = 'violet' | 'teal' | 'emerald' | 'indigo'
18
-
19
36
  export interface ReleaseNotes {
20
37
  version: string
21
- /** The first highlight leads the dialog at full width. */
22
38
  highlights: ReleaseHighlight[]
23
39
  improvements: string[]
24
- releaseUrl: string
25
40
  }
26
41
 
27
- // One entry per release that has something to announce. A version without its
28
- // own entry shows the newest entry at or below it, so a patch upgrade (or one
29
- // that skips the release with notes) still gets them. Every minor and major
30
- // release must have its own entry: scripts/check-whats-new.sh refuses to
42
+ export const CHANGELOG_URL = 'https://radarhq.io/changelog'
43
+
44
+ // One entry per release that has something to announce. A user sees the
45
+ // entries for the releases they haven't seen yet, composed into one dialog, so
46
+ // a patch upgrade, or one that skips releases, still gets them. Every minor and
47
+ // major release must have its own entry: scripts/check-whats-new.sh refuses to
31
48
  // release one without it.
32
49
  export const RELEASE_NOTES: ReleaseNotes[] = [
50
+ {
51
+ version: 'v1.16.0',
52
+ highlights: [
53
+ {
54
+ id: 'restart-loop-issues',
55
+ icon: RefreshCcw,
56
+ title: 'One steady issue per crash loop',
57
+ description: 'A crash-looping container stays one issue until it recovers, with probe failures and exits as evidence. No more repeat alerts.',
58
+ leadDescription: 'A restarting container is one issue until it recovers, instead of a new one on every check, so no more repeat alerts and investigations. Probe failures, the last exit and how many replicas are affected sit on it as evidence, and severity follows impact.',
59
+ importance: 6,
60
+ path: '/issues',
61
+ cta: 'Open Issues',
62
+ },
63
+ {
64
+ id: 'log-viewer',
65
+ icon: ScrollText,
66
+ title: 'Log viewer enhancements',
67
+ description: 'Levels from the logger itself, stack traces kept with their errors, Hide matching, and a chip for lines without a level.',
68
+ importance: 5,
69
+ },
70
+ {
71
+ id: 'per-cluster-settings',
72
+ icon: SlidersHorizontal,
73
+ title: 'Separate settings for each cluster',
74
+ description: 'Running Radar locally, each kubeconfig context keeps its own Metrics, Argo CD and Cost connections.',
75
+ importance: 4,
76
+ },
77
+ ],
78
+ improvements: [
79
+ // The grid fills row by row: pair lines of similar length.
80
+ "A workload's Timeline shows only its own history",
81
+ "Timeline's Problems filter no longer hides crash loops",
82
+ 'Live Traffic figures corrected for Cilium and Istio',
83
+ 'Saved namespace picks survive a failing namespace list',
84
+ 'Terminal tabs show and keep the context they opened for',
85
+ 'YAML review flags a resource that changed after review',
86
+ 'Helm chart meets the Pod Security restricted profile',
87
+ 'Helm chart extraArgs passes through any Radar flag',
88
+ ],
89
+ },
33
90
  {
34
91
  version: 'v1.15.0',
35
- releaseUrl: 'https://github.com/skyhook-io/radar/releases/tag/v1.15.0',
36
92
  highlights: [
37
93
  {
38
94
  id: 'ai-investigations',
@@ -40,14 +96,16 @@ export const RELEASE_NOTES: ReleaseNotes[] = [
40
96
  title: 'AI investigations that show their work',
41
97
  // No link: the investigations page redirects home when this run
42
98
  // mode can't host local agents.
43
- description: 'A clear verdict and the story of what broke, with the charts, logs and config behind it placed where the agent cites them. Now also runs on OpenCode, including AWS Bedrock.',
99
+ description: 'A clear verdict and the story of what broke, with the evidence placed where the agent cites it. Runs on OpenCode too.',
100
+ leadDescription: 'A clear verdict and the story of what broke, with the charts, logs and config behind it placed where the agent cites them. Runs on OpenCode too, including AWS Bedrock.',
101
+ importance: 9,
44
102
  },
45
103
  {
46
104
  id: 'gitops-manifest-diff',
47
105
  icon: GitCompareArrows,
48
106
  title: 'Full manifest diffs for Argo CD resources',
49
107
  description: 'Compare the Git-rendered and live manifest of a drifted Argo CD resource, side by side or unified.',
50
- tone: 'violet',
108
+ importance: 8,
51
109
  path: '/gitops',
52
110
  cta: 'Open GitOps',
53
111
  },
@@ -56,7 +114,7 @@ export const RELEASE_NOTES: ReleaseNotes[] = [
56
114
  icon: Scale,
57
115
  title: 'Rightsizing and cost data in MCP',
58
116
  description: 'Agents get new optimized tools. Risks (OOM, limit conflicts and throttling) lead before cost-saving, in the UI as well.',
59
- tone: 'emerald',
117
+ importance: 7,
60
118
  path: '/cost/rightsizing',
61
119
  cta: 'Open Rightsizing',
62
120
  },
@@ -65,14 +123,14 @@ export const RELEASE_NOTES: ReleaseNotes[] = [
65
123
  icon: Layers,
66
124
  title: 'Deeper batch + AI/ML workloads support',
67
125
  description: 'Dedicated pages for Kueue, JobSet and Ray resources, and Job and JobSet details now show why a queued job has no Pods.',
68
- tone: 'teal',
126
+ importance: 7,
69
127
  },
70
128
  {
71
129
  id: 'rollout-traffic-topology',
72
130
  icon: Network,
73
131
  title: 'Canary and blue-green in Topology',
74
132
  description: 'See canary and stable Pods, which Services route to each side, and the canary traffic weight.',
75
- tone: 'indigo',
133
+ importance: 7,
76
134
  path: '/topology',
77
135
  cta: 'Open Topology',
78
136
  },
@@ -105,17 +163,90 @@ export function releaseLine(version: string): string {
105
163
  return version.replace(/^(v\d+\.\d+)\.\d+$/, '$1')
106
164
  }
107
165
 
108
- /** The newest notes for a release at or below `version` — what that version's user has access to. */
166
+ /** The entries at or below `version`, newest first: what that version's user has access to. */
167
+ function releasesUpTo(version: string, catalog: ReleaseNotes[]): ReleaseNotes[] {
168
+ return catalog
169
+ .filter(notes => (compareVersions(notes.version, version) ?? 1) <= 0)
170
+ .sort((a, b) => compareVersions(b.version, a.version) ?? 0)
171
+ }
172
+
173
+ /** The newest notes for a release at or below `version`. */
109
174
  export function latestReleaseNotesFor(
110
175
  version: string | undefined,
111
176
  catalog: ReleaseNotes[] = RELEASE_NOTES,
112
177
  ): ReleaseNotes | undefined {
113
178
  if (!version) return undefined
114
- let latest: ReleaseNotes | undefined
115
- for (const notes of catalog) {
116
- const atOrBelow = compareVersions(notes.version, version)
117
- if (atOrBelow === null || atOrBelow > 0) continue
118
- if (!latest || (compareVersions(notes.version, latest.version) ?? 0) > 0) latest = notes
179
+ return releasesUpTo(version, catalog)[0]
180
+ }
181
+
182
+ /**
183
+ * The entries a user hasn't seen, newest first: those after `lastSeen`, up to
184
+ * the running version. A fresh install has nothing to compare against and gets
185
+ * none; an install that predates the seen record, or whose record isn't a
186
+ * version, gets them all.
187
+ */
188
+ export function unreadReleases(
189
+ currentVersion: string,
190
+ lastSeen: string | null,
191
+ priorInstall: boolean,
192
+ catalog: ReleaseNotes[] = RELEASE_NOTES,
193
+ ): ReleaseNotes[] {
194
+ const available = releasesUpTo(currentVersion, catalog)
195
+ if (lastSeen === null) return priorInstall ? available : []
196
+ return available.filter(notes => (compareVersions(notes.version, lastSeen) ?? 1) > 0)
197
+ }
198
+
199
+ // How much a highlight counts by how far back its release is. A user who
200
+ // skipped releases sees the best of them, weighted toward the newest; older
201
+ // releases than this list covers are left to the changelog.
202
+ export const RELEASE_WEIGHTS = [1, 0.75, 0.5]
203
+ export const MAX_CARDS = 5
204
+ export const MAX_LINES = 8
205
+ // The dialog opens by itself only when the cards it shows add up to this.
206
+ // A release of mostly 4-6s stays behind the unread dot; one with a few solid
207
+ // features, or a skipped release with them, opens it.
208
+ export const AUTO_OPEN_SCORE = 25
209
+
210
+ export interface ComposedHighlight extends ReleaseHighlight {
211
+ version: string
212
+ score: number
213
+ }
214
+
215
+ export interface ComposedLine {
216
+ version: string
217
+ text: string
218
+ }
219
+
220
+ export interface ComposedNotes {
221
+ /** The releases shown, newest first. */
222
+ versions: string[]
223
+ /** The first one leads the dialog at full width. */
224
+ highlights: ComposedHighlight[]
225
+ lines: ComposedLine[]
226
+ /** Sum of the shown highlights' scores. */
227
+ score: number
228
+ /** False when older unseen releases were left out. */
229
+ complete: boolean
230
+ }
231
+
232
+ /** One dialog from releases ordered newest first. */
233
+ export function composeReleaseNotes(releases: ReleaseNotes[]): ComposedNotes {
234
+ const shown = releases.slice(0, RELEASE_WEIGHTS.length)
235
+ // Built newest release first, in authored order, so the stable sort breaks
236
+ // ties the same way.
237
+ const ranked = shown
238
+ .flatMap((notes, i) => notes.highlights.map(h => ({ ...h, version: notes.version, score: h.importance * RELEASE_WEIGHTS[i] })))
239
+ .sort((a, b) => b.score - a.score)
240
+ const highlights = ranked.slice(0, MAX_CARDS)
241
+ const lines: ComposedLine[] = [
242
+ ...ranked.slice(MAX_CARDS).map(h => ({ version: h.version, text: h.title })),
243
+ ...shown.flatMap(notes => notes.improvements.map(text => ({ version: notes.version, text }))),
244
+ ].slice(0, MAX_LINES)
245
+ return {
246
+ versions: shown.map(notes => notes.version),
247
+ highlights,
248
+ lines,
249
+ score: highlights.reduce((sum, h) => sum + h.score, 0),
250
+ complete: shown.length === releases.length,
119
251
  }
120
- return latest
121
252
  }
@@ -2,7 +2,8 @@ import { describe, expect, it } from 'vitest'
2
2
 
3
3
  import { gitOpsOwnerFromRelationships } from '@skyhook-io/k8s-ui'
4
4
  import type { Relationships } from '../../types'
5
- import { findInheritedGitOpsLookupRef, supportsBatchExecution } from './WorkloadView'
5
+ import { supportsBatchExecution } from './WorkloadView'
6
+ import { findInheritedGitOpsLookupRef } from '../../hooks/useResolvedGitOpsOwner'
6
7
 
7
8
  describe('findInheritedGitOpsLookupRef', () => {
8
9
  it('follows a referenced ReplicaSet to its parent workload for inherited ownership', () => {