@skyhook-io/radar-app 1.8.0 → 1.8.2

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 (32) hide show
  1. package/package.json +2 -2
  2. package/src/App.tsx +95 -16
  3. package/src/api/client.ts +64 -3
  4. package/src/components/DebugOverlay.tsx +1 -1
  5. package/src/components/applications/ApplicationsView.tsx +1 -1
  6. package/src/components/compare/CompareViewRoute.tsx +13 -5
  7. package/src/components/cost/CostTrendChart.tsx +3 -3
  8. package/src/components/cost/CostView.tsx +1 -1
  9. package/src/components/helm/ChartBrowser.tsx +1 -1
  10. package/src/components/helm/HelmReleaseDrawer.tsx +233 -19
  11. package/src/components/helm/HelmView.tsx +85 -16
  12. package/src/components/helm/InstallWizard.tsx +1 -1
  13. package/src/components/helm/RevisionHistory.tsx +46 -2
  14. package/src/components/helm/TrackChartSourceDialog.tsx +141 -0
  15. package/src/components/home/ActivitySummary.tsx +4 -1
  16. package/src/components/home/TrafficSummary.tsx +2 -2
  17. package/src/components/home/mcpToolCatalog.ts +5 -5
  18. package/src/components/issues/IssuesPane.tsx +2 -2
  19. package/src/components/nav/PrimaryNavRail.tsx +1 -1
  20. package/src/components/portforward/PortForwardManager.tsx +70 -8
  21. package/src/components/resource/PrometheusChartsGrid.tsx +1 -1
  22. package/src/components/resources/ResourcesView.tsx +2 -0
  23. package/src/components/settings/MyPermissionsDialog.tsx +64 -4
  24. package/src/components/shared/LargeClusterNamespacePicker.tsx +1 -1
  25. package/src/components/traffic/TrafficGraph.tsx +29 -20
  26. package/src/components/traffic/TrafficView.tsx +3 -3
  27. package/src/components/ui/DiagnosticsOverlay.tsx +1 -1
  28. package/src/components/ui/ShortcutHelpOverlay.tsx +1 -1
  29. package/src/components/ui/command-items.ts +1 -1
  30. package/src/components/workload/WorkloadView.tsx +17 -17
  31. package/src/context/ConnectionContext.tsx +29 -2
  32. package/src/main.tsx +1 -1
@@ -29,7 +29,7 @@ const VIEW_LABELS: Record<string, string> = {
29
29
  timeline: 'Timeline',
30
30
  helm: 'Helm',
31
31
  gitops: 'GitOps',
32
- traffic: 'Traffic',
32
+ traffic: 'Live Traffic',
33
33
  }
34
34
 
35
35
  type ShortcutEntry = { description: string; keys: string[] }
@@ -91,7 +91,7 @@ const VIEW_ENTRIES: { view: MainView; label: string; icon: React.ComponentType<{
91
91
  { view: 'timeline', label: 'Timeline', icon: Clock, shortcut: 'g l' },
92
92
  { view: 'helm', label: 'Helm', icon: Package, shortcut: 'g m' },
93
93
  { view: 'gitops', label: 'GitOps', icon: GitBranch, shortcut: 'g o' },
94
- { view: 'traffic', label: 'Traffic', icon: Activity, shortcut: 'g f' },
94
+ { view: 'traffic', label: 'Live Traffic', icon: Activity, shortcut: 'g f' },
95
95
  { view: 'checks', label: 'Checks', icon: ShieldCheck, shortcut: 'g u' },
96
96
  { view: 'cost', label: 'Cost', icon: DollarSign, shortcut: 'g c' },
97
97
  ]
@@ -199,45 +199,45 @@ function useActionsBarProps(kind: string, namespace: string, name: string) {
199
199
  renderPortForward: ({ type, namespace: ns, name: n, className }: { type: 'pod' | 'service'; namespace: string; name: string; className?: string }) => (
200
200
  <PortForwardButton type={type} namespace={ns} name={n} className={className} />
201
201
  ),
202
- onDelete: (params: any, callbacks?: any) => deleteMutation.mutate(params, { onSuccess: callbacks?.onSuccess }),
202
+ onDelete: (params: Parameters<typeof deleteMutation.mutate>[0], callbacks?: { onSuccess?: () => void }) => deleteMutation.mutate(params, { onSuccess: callbacks?.onSuccess }),
203
203
  isDeleting: deleteMutation.isPending,
204
204
  cascadeDependents: cascadePreview?.dependents,
205
205
  cascadeLoading,
206
- onRestart: (params: any) => restartWorkloadMutation.mutate(params),
206
+ onRestart: (params: Parameters<typeof restartWorkloadMutation.mutate>[0]) => restartWorkloadMutation.mutate(params),
207
207
  isRestarting: restartWorkloadMutation.isPending,
208
208
  revisions: revisionsList,
209
209
  revisionsLoading,
210
210
  revisionsError: revisionsError ?? null,
211
- onRollback: (params: any, callbacks?: any) => rollbackMutation.mutate(params, { onSuccess: callbacks?.onSuccess }),
211
+ onRollback: (params: Parameters<typeof rollbackMutation.mutate>[0], callbacks?: { onSuccess?: () => void }) => rollbackMutation.mutate(params, { onSuccess: callbacks?.onSuccess }),
212
212
  isRollingBack: rollbackMutation.isPending,
213
- onTriggerCronJob: (params: any) => triggerCronJobMutation.mutate(params),
213
+ onTriggerCronJob: (params: Parameters<typeof triggerCronJobMutation.mutate>[0]) => triggerCronJobMutation.mutate(params),
214
214
  isTriggeringCronJob: triggerCronJobMutation.isPending,
215
- onSuspendCronJob: (params: any) => suspendCronJobMutation.mutate(params),
215
+ onSuspendCronJob: (params: Parameters<typeof suspendCronJobMutation.mutate>[0]) => suspendCronJobMutation.mutate(params),
216
216
  isSuspendingCronJob: suspendCronJobMutation.isPending,
217
- onResumeCronJob: (params: any) => resumeCronJobMutation.mutate(params),
217
+ onResumeCronJob: (params: Parameters<typeof resumeCronJobMutation.mutate>[0]) => resumeCronJobMutation.mutate(params),
218
218
  isResumingCronJob: resumeCronJobMutation.isPending,
219
- onFluxReconcile: (params: any) => fluxReconcileMutation.mutate(params),
219
+ onFluxReconcile: (params: Parameters<typeof fluxReconcileMutation.mutate>[0]) => fluxReconcileMutation.mutate(params),
220
220
  isFluxReconciling: fluxReconcileMutation.isPending,
221
- onFluxSyncWithSource: (params: any) => fluxSyncWithSourceMutation.mutate(params),
221
+ onFluxSyncWithSource: (params: Parameters<typeof fluxSyncWithSourceMutation.mutate>[0]) => fluxSyncWithSourceMutation.mutate(params),
222
222
  isFluxSyncing: fluxSyncWithSourceMutation.isPending,
223
- onFluxSuspend: (params: any) => fluxSuspendMutation.mutate(params),
223
+ onFluxSuspend: (params: Parameters<typeof fluxSuspendMutation.mutate>[0]) => fluxSuspendMutation.mutate(params),
224
224
  isFluxSuspending: fluxSuspendMutation.isPending,
225
- onFluxResume: (params: any) => fluxResumeMutation.mutate(params),
225
+ onFluxResume: (params: Parameters<typeof fluxResumeMutation.mutate>[0]) => fluxResumeMutation.mutate(params),
226
226
  isFluxResuming: fluxResumeMutation.isPending,
227
- onArgoSync: (params: any) => argoSyncMutation.mutate(params),
227
+ onArgoSync: (params: Parameters<typeof argoSyncMutation.mutate>[0]) => argoSyncMutation.mutate(params),
228
228
  isArgoSyncing: argoSyncMutation.isPending,
229
- onArgoRefresh: (params: any) => argoRefreshMutation.mutate(params),
229
+ onArgoRefresh: (params: Parameters<typeof argoRefreshMutation.mutate>[0]) => argoRefreshMutation.mutate(params),
230
230
  isArgoRefreshing: argoRefreshMutation.isPending,
231
- onArgoSuspend: (params: any) => argoSuspendMutation.mutate(params),
231
+ onArgoSuspend: (params: Parameters<typeof argoSuspendMutation.mutate>[0]) => argoSuspendMutation.mutate(params),
232
232
  isArgoSuspending: argoSuspendMutation.isPending,
233
- onArgoResume: (params: any) => argoResumeMutation.mutate(params),
233
+ onArgoResume: (params: Parameters<typeof argoResumeMutation.mutate>[0]) => argoResumeMutation.mutate(params),
234
234
  isArgoResuming: argoResumeMutation.isPending,
235
235
  canNodeWrite,
236
- onCordonNode: (params: any) => cordonMutation.mutate(params),
236
+ onCordonNode: (params: Parameters<typeof cordonMutation.mutate>[0]) => cordonMutation.mutate(params),
237
237
  isCordoningNode: cordonMutation.isPending,
238
- onUncordonNode: (params: any) => uncordonMutation.mutate(params),
238
+ onUncordonNode: (params: Parameters<typeof uncordonMutation.mutate>[0]) => uncordonMutation.mutate(params),
239
239
  isUncordoningNode: uncordonMutation.isPending,
240
- onDrainNode: (params: any) => drainMutation.mutate(params),
240
+ onDrainNode: (params: Parameters<typeof drainMutation.mutate>[0]) => drainMutation.mutate(params),
241
241
  isDrainingNode: drainMutation.isPending,
242
242
  }
243
243
  }
@@ -62,6 +62,22 @@ export function ConnectionProvider({ children }: { children: ReactNode }) {
62
62
  // Track if SSE has started delivering connection_state events
63
63
  // Once SSE is active, it becomes the authoritative source for connection state
64
64
  const sseActiveRef = useRef(false)
65
+ // Track whether we've reached 'connected' at least once. Distinguishes the
66
+ // initial connect (bootstrap queries already fetched while 'connecting') from
67
+ // a reconnect after a drop (cache may be stale across the gap).
68
+ const hasConnectedRef = useRef(false)
69
+ // Whether the QueryClient already held data when this provider mounted. A host
70
+ // can share one client across cluster-scoped RadarApp mounts (see RadarApp's
71
+ // `queryClient` prop); that client may carry another cluster's data under
72
+ // identical keys, so a warm-at-mount cache must be fully refreshed on first
73
+ // connect. A cold cache (standalone, or a per-cluster remount) takes the cheap
74
+ // error-only path. Snapshot synchronously before this provider's own query
75
+ // registers — ConnectionProvider is the outermost provider, so a fresh client
76
+ // is genuinely empty here.
77
+ const cacheWarmAtMountRef = useRef<boolean | null>(null)
78
+ if (cacheWarmAtMountRef.current === null) {
79
+ cacheWarmAtMountRef.current = queryClient.getQueryCache().getAll().length > 0
80
+ }
65
81
 
66
82
  // Fetch initial connection status
67
83
  // Poll while connecting to get progress updates (SSE not established yet)
@@ -143,9 +159,20 @@ export function ConnectionProvider({ children }: { children: ReactNode }) {
143
159
  return status
144
160
  })
145
161
 
146
- // If we just connected, invalidate queries to fetch fresh data
147
162
  if (status.state === 'connected') {
148
- queryClient.invalidateQueries()
163
+ const firstConnect = !hasConnectedRef.current
164
+ hasConnectedRef.current = true
165
+ // A reconnect after a drop (cache stale across the gap), or a first connect
166
+ // onto a client that already carried data at mount (shared across clusters),
167
+ // refreshes the whole cache. A clean first connect only needs to recover the
168
+ // bootstrap queries that 503'd while the cluster was still 'connecting'
169
+ // (status === 'error'); the rest already fetched fresh during 'connecting',
170
+ // so re-fetching the whole cache there would double-load every endpoint.
171
+ if (!firstConnect || cacheWarmAtMountRef.current) {
172
+ queryClient.invalidateQueries()
173
+ } else {
174
+ queryClient.invalidateQueries({ predicate: (q) => q.state.status === 'error' })
175
+ }
149
176
  }
150
177
  }, [queryClient])
151
178
 
package/src/main.tsx CHANGED
@@ -130,7 +130,7 @@ document.execCommand = function (command: string, showUI?: boolean, value?: stri
130
130
  dt.setData('text/plain', text)
131
131
  const ev = new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })
132
132
  if (!el.dispatchEvent(ev)) return
133
- } catch (_e) { /* ClipboardEvent dispatch failed, fall back to insertText */ }
133
+ } catch { /* ClipboardEvent dispatch failed, fall back to insertText */ }
134
134
  _origExecCommand('insertText', false, text)
135
135
  }).catch((err) => { console.warn('[Radar] Paste failed:', err) })
136
136
  return true