@skyhook-io/radar-app 1.8.3 → 1.8.6

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 +5 -5
  2. package/src/App.tsx +256 -94
  3. package/src/RadarApp.tsx +4 -1
  4. package/src/api/client.metrics.test.ts +106 -0
  5. package/src/api/client.ts +147 -20
  6. package/src/components/ConnectionErrorView.tsx +1 -1
  7. package/src/components/ContextSwitcher.tsx +5 -1
  8. package/src/components/NamespaceSwitcher.tsx +21 -300
  9. package/src/components/applications/ApplicationsView.tsx +22 -7
  10. package/src/components/audit/AuditView.tsx +11 -2
  11. package/src/components/cost/CostView.tsx +12 -2
  12. package/src/components/gitops/GitOpsView.tsx +22 -7
  13. package/src/components/helm/HelmCompareRoute.tsx +1342 -0
  14. package/src/components/helm/HelmReleaseDrawer.tsx +189 -352
  15. package/src/components/helm/HelmView.tsx +79 -62
  16. package/src/components/helm/ManifestDiffViewer.tsx +4 -4
  17. package/src/components/home/ClusterHealthCard.tsx +6 -1
  18. package/src/components/home/HomeView.tsx +20 -7
  19. package/src/components/home/mcpToolCatalog.ts +1 -1
  20. package/src/components/issues/IssuesPane.tsx +29 -18
  21. package/src/components/resources/ResourceDetailDrawer.tsx +8 -3
  22. package/src/components/resources/ResourcesView.tsx +3 -0
  23. package/src/components/resources/renderers/NodeRenderer.tsx +10 -4
  24. package/src/components/resources/renderers/PodRenderer.tsx +10 -4
  25. package/src/components/timeline/TimelineView.tsx +26 -2
  26. package/src/components/traffic/TrafficView.tsx +17 -10
  27. package/src/components/ui/Markdown.tsx +2 -2
  28. package/src/components/ui/Omnibar.tsx +1 -1
  29. package/src/components/workload/WorkloadView.tsx +5 -1
  30. package/src/filter/FilterLocationBridge.tsx +30 -0
  31. package/src/hooks/useKeyboardShortcuts.tsx +1 -0
  32. package/src/index.ts +15 -0
@@ -1,5 +1,4 @@
1
1
  import { useState, useEffect, useMemo, useRef, useCallback } from 'react'
2
- import { useRefreshAnimation } from '../../hooks/useRefreshAnimation'
3
2
  import { useTrafficSources, useTrafficFlows, useTrafficConnect, useSetTrafficSource } from '../../api/traffic'
4
3
  import { useClusterInfo } from '../../api/client'
5
4
  import type { TrafficWizardState, AggregatedFlow } from '../../types'
@@ -7,11 +6,12 @@ import { TrafficWizard } from './TrafficWizard'
7
6
  import { TrafficGraph, type TrafficGraphSelection } from './TrafficGraph'
8
7
  import { TrafficFilterSidebar } from './TrafficFilterSidebar'
9
8
  import { TrafficFlowListProvider } from './TrafficFlowListContext'
10
- import { Loader2, RefreshCw, Filter, Plug, ChevronDown, List, Activity, AlertTriangle } from 'lucide-react'
9
+ import { Loader2, Filter, Plug, ChevronDown, List, Activity, AlertTriangle } from 'lucide-react'
11
10
  import { clsx } from 'clsx'
12
11
  import { useQueryClient } from '@tanstack/react-query'
13
12
  import { useDock } from '../dock'
14
- import { EmptyState, PaneLoader } from '@skyhook-io/k8s-ui'
13
+ import { EmptyState, PaneLoader, FreshnessControl } from '@skyhook-io/k8s-ui'
14
+ import { useConnection } from '../../context/ConnectionContext'
15
15
  import { Tooltip } from '../ui/Tooltip'
16
16
 
17
17
  // Addon types for filtering
@@ -339,6 +339,7 @@ interface TrafficViewProps {
339
339
  }
340
340
 
341
341
  export function TrafficView({ namespaces }: TrafficViewProps) {
342
+ const { connection } = useConnection()
342
343
  const [wizardState, setWizardState] = useState<TrafficWizardState>('detecting')
343
344
  const [timeRange, setTimeRange] = useState<string>('5m')
344
345
  const [hideSystem, setHideSystem] = useState(true)
@@ -421,8 +422,8 @@ export function TrafficView({ namespaces }: TrafficViewProps) {
421
422
 
422
423
  const {
423
424
  data: flowsData,
424
- isLoading: flowsLoading,
425
425
  isFetching: flowsFetching,
426
+ dataUpdatedAt: flowsUpdatedAt,
426
427
  refetch: refetchFlowsRaw,
427
428
  } = useTrafficFlows({
428
429
  namespaces,
@@ -430,7 +431,6 @@ export function TrafficView({ namespaces }: TrafficViewProps) {
430
431
  // Only fetch flows when connected (not connecting and no connection error)
431
432
  enabled: wizardState === 'ready' && !isConnecting && !connectionError,
432
433
  })
433
- const [refetchFlows, isRefreshAnimating] = useRefreshAnimation(refetchFlowsRaw)
434
434
 
435
435
  // Auto-retry when flows return with warning but no data (e.g., port-forward not ready yet)
436
436
  useEffect(() => {
@@ -1125,12 +1125,19 @@ export function TrafficView({ namespaces }: TrafficViewProps) {
1125
1125
  </button>
1126
1126
  </Tooltip>
1127
1127
  )}
1128
- <div className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-theme-surface/90 backdrop-blur border border-theme-border text-[10px] text-theme-text-tertiary">
1128
+ <div className="flex items-center px-2 py-1 rounded-lg bg-theme-surface/90 backdrop-blur border border-theme-border text-[10px] text-theme-text-tertiary tabular-nums">
1129
1129
  {flowStats.shown}/{flowStats.total}
1130
- <button onClick={refetchFlows} disabled={flowsLoading || isRefreshAnimating}
1131
- className={clsx('p-0.5 rounded hover:text-theme-text-primary transition-colors', (flowsLoading || isRefreshAnimating) && 'opacity-50')}>
1132
- {flowsLoading ? <Loader2 className="h-3 w-3 animate-spin" /> : <RefreshCw className={clsx('h-3 w-3', isRefreshAnimating && 'animate-spin')} />}
1133
- </button>
1130
+ </div>
1131
+ {/* Flows are a REST snapshot (no poll, no stream), so this is
1132
+ an honest "Updated N ago" + manual refresh not "live". */}
1133
+ <div className="flex items-center rounded-lg bg-theme-surface/90 backdrop-blur border border-theme-border px-1.5 py-0.5">
1134
+ <FreshnessControl
1135
+ mode="snapshot"
1136
+ dataUpdatedAt={flowsUpdatedAt}
1137
+ isFetching={flowsFetching}
1138
+ onRefresh={() => refetchFlowsRaw()}
1139
+ connectionState={connection.state}
1140
+ />
1134
1141
  </div>
1135
1142
  </div>
1136
1143
  </>
@@ -39,10 +39,10 @@ export function Markdown({ children, className }: MarkdownProps) {
39
39
  </a>
40
40
  ),
41
41
  ul: ({ children }) => (
42
- <ul className="list-disc list-inside my-2 space-y-1 text-theme-text-secondary">{children}</ul>
42
+ <ul className="list-disc list-outside pl-4 my-2 space-y-1 text-theme-text-secondary">{children}</ul>
43
43
  ),
44
44
  ol: ({ children }) => (
45
- <ol className="list-decimal list-inside my-2 space-y-1 text-theme-text-secondary">{children}</ol>
45
+ <ol className="list-decimal list-outside pl-4 my-2 space-y-1 text-theme-text-secondary">{children}</ol>
46
46
  ),
47
47
  li: ({ children }) => (
48
48
  <li className="leading-relaxed">{children}</li>
@@ -414,7 +414,7 @@ export const Omnibar = forwardRef<OmnibarHandle, OmnibarProps>(function Omnibar(
414
414
  return (
415
415
  <div
416
416
  ref={containerRef}
417
- className={clsx('relative w-full', hero ? 'max-w-3xl' : 'max-w-xl', open && hero && 'z-[16]')}
417
+ className={clsx('relative w-full', hero ? 'max-w-3xl' : 'max-w-lg', open && hero && 'z-[16]')}
418
418
  // Open on click even when the field is already focused — onFocus alone
419
419
  // never fires again, so an autofocused hero (Home) wouldn't reveal the
420
420
  // launcher on a click.
@@ -161,8 +161,12 @@ interface WorkloadViewProps {
161
161
  onNavigateToResource?: NavigateToResource
162
162
  onCollapseToDrawer?: () => void
163
163
  expanded?: boolean
164
+ /** false on the outgoing layer during an expand/collapse crossfade (default true) */
165
+ active?: boolean
164
166
  onClose?: () => void
165
- onExpand?: () => void
167
+ onExpand?: (opts?: { yaml?: boolean }) => void
168
+ onExpandIntent?: () => void
169
+ onCancelExpandIntent?: () => void
166
170
  initialTab?: 'detail' | 'yaml'
167
171
  group?: string
168
172
  }
@@ -0,0 +1,30 @@
1
+ import { useCallback, useMemo, useRef, type ReactNode } from 'react';
2
+ import { useSearchParams } from 'react-router-dom';
3
+ import { FilterLocationProvider, type FilterLocation } from '@skyhook-io/k8s-ui';
4
+
5
+ // Adapts OSS Radar's react-router search params to the app-agnostic
6
+ // FilterLocation seam that @skyhook-io/k8s-ui's useFilterState reads. Mounted
7
+ // once inside the router; every list view's shared filter state flows through
8
+ // it, keeping the URL the single source of truth. (Radar Hub provides its own
9
+ // bridge over its router — k8s-ui itself never depends on react-router.)
10
+ export function FilterLocationBridge({ children }: { children: ReactNode }) {
11
+ const [searchParams, setSearchParams] = useSearchParams();
12
+
13
+ // React Router's functional updater is NOT state-queued: two updates in one
14
+ // tick can both read the same params and clobber. Advance a ref synchronously
15
+ // so successive filter changes (e.g. toggling two facets fast) compose.
16
+ const latest = useRef(searchParams);
17
+ latest.current = searchParams;
18
+
19
+ const update = useCallback<FilterLocation['update']>(
20
+ (updater, opts) => {
21
+ const next = updater(new URLSearchParams(latest.current));
22
+ latest.current = next;
23
+ setSearchParams(next, opts);
24
+ },
25
+ [setSearchParams],
26
+ );
27
+
28
+ const value = useMemo<FilterLocation>(() => ({ searchParams, update }), [searchParams, update]);
29
+ return <FilterLocationProvider value={value}>{children}</FilterLocationProvider>;
30
+ }
@@ -3,5 +3,6 @@ export {
3
3
  useRegisterShortcut,
4
4
  useRegisterShortcuts,
5
5
  useActiveShortcuts,
6
+ useSuppressBaseShortcuts,
6
7
  } from '@skyhook-io/k8s-ui'
7
8
  export type { KeyboardShortcut, ShortcutCategory } from '@skyhook-io/k8s-ui'
package/src/index.ts CHANGED
@@ -23,6 +23,21 @@ export { ShortcutHelpOverlay } from './components/ui/ShortcutHelpOverlay';
23
23
  export { ClusterSwitcher } from '@skyhook-io/k8s-ui';
24
24
  export type { ClusterSwitcherProps, ClusterSwitcherItem } from '@skyhook-io/k8s-ui';
25
25
 
26
+ // Shared namespace-scope picker primitive — re-exported so embedders (Radar
27
+ // Hub) can render a namespace filter visually identical to OSS Radar's, driving
28
+ // their own per-cluster scope (Hub via ?namespaces= on the embedded RadarApp).
29
+ export { NamespacePicker } from '@skyhook-io/k8s-ui';
30
+ export type {
31
+ NamespacePickerProps,
32
+ NamespacePickerHandle,
33
+ NamespaceScopeView,
34
+ } from '@skyhook-io/k8s-ui';
35
+
36
+ // Shared bordered shell that groups the cluster + namespace segments into one
37
+ // pill — so Radar Hub's cluster top bar matches OSS Radar's header exactly.
38
+ export { ScopePill } from '@skyhook-io/k8s-ui';
39
+ export type { ScopePillProps } from '@skyhook-io/k8s-ui';
40
+
26
41
  // Deep-link builders — so consumers (Radar Hub) construct deep links into a
27
42
  // cluster view without hand-rolling Radar's internal URL format, which drifts
28
43
  // silently when Radar re-routes. `resourcePath` opens the detail drawer for any