@skyhook-io/radar-app 1.7.0 → 1.8.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.
- package/package.json +8 -2
- package/src/App.tsx +58 -30
- package/src/components/ConnectionErrorView.tsx +5 -2
- package/src/components/DebugOverlay.tsx +4 -2
- package/src/components/UserMenu.tsx +5 -2
- package/src/components/audit/AuditSettingsDialog.tsx +14 -10
- package/src/components/audit/AuditView.tsx +3 -1
- package/src/components/compare/useCompareLauncher.tsx +6 -3
- package/src/components/cost/CostView.tsx +13 -4
- package/src/components/helm/ChartBrowser.tsx +6 -4
- package/src/components/helm/HelmReleaseDrawer.tsx +25 -13
- package/src/components/helm/HelmView.tsx +3 -2
- package/src/components/helm/InstallWizard.tsx +3 -2
- package/src/components/helm/OwnedResources.tsx +24 -13
- package/src/components/helm/RevisionHistory.tsx +2 -1
- package/src/components/helm/ValuesViewer.tsx +4 -2
- package/src/components/home/ClusterHealthCard.tsx +21 -18
- package/src/components/home/HelmSummary.tsx +3 -1
- package/src/components/home/MCPSetupDialog.tsx +10 -4
- package/src/components/nav/PrimaryNavRail.tsx +5 -2
- package/src/components/portforward/PortForwardButton.tsx +14 -8
- package/src/components/resource/PrometheusChartsGrid.tsx +9 -2
- package/src/components/resource/RestartChart.tsx +6 -5
- package/src/components/resource/RightsizingStrip.tsx +4 -1
- package/src/components/resource-drawer/ResourceDrawer.tsx +3 -1
- package/src/components/resources/ImageFilesystemModal.tsx +16 -8
- package/src/components/resources/PodFilesystemModal.tsx +4 -2
- package/src/components/settings/SettingsDialog.tsx +371 -141
- package/src/components/traffic/TrafficFilterSidebar.tsx +8 -43
- package/src/components/traffic/TrafficFlowList.tsx +13 -4
- package/src/components/traffic/TrafficGraph.tsx +8 -3
- package/src/components/traffic/TrafficView.tsx +4 -2
- package/src/components/ui/Omnibar.tsx +0 -1
- package/src/components/workload/WorkloadView.tsx +15 -10
- package/src/context/NavCustomization.tsx +19 -5
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { memo, useState
|
|
2
|
-
import { createPortal } from 'react-dom'
|
|
1
|
+
import { memo, useState } from 'react'
|
|
3
2
|
import {
|
|
4
3
|
ChevronDown,
|
|
5
4
|
Eye,
|
|
@@ -16,43 +15,7 @@ import { clsx } from 'clsx'
|
|
|
16
15
|
import { SEVERITY_BADGE } from '@skyhook-io/k8s-ui/utils/badge-colors'
|
|
17
16
|
import type { AddonMode } from './TrafficView'
|
|
18
17
|
import { getNamespaceColor } from '../../utils/traffic-colors'
|
|
19
|
-
|
|
20
|
-
// Fast tooltip component using portal to escape overflow
|
|
21
|
-
function Tooltip({ children, content }: { children: React.ReactNode; content: string }) {
|
|
22
|
-
const [show, setShow] = useState(false)
|
|
23
|
-
const [pos, setPos] = useState({ x: 0, y: 0 })
|
|
24
|
-
const ref = useRef<HTMLDivElement>(null)
|
|
25
|
-
|
|
26
|
-
const handleMouseEnter = () => {
|
|
27
|
-
if (ref.current) {
|
|
28
|
-
const rect = ref.current.getBoundingClientRect()
|
|
29
|
-
setPos({ x: rect.right + 8, y: rect.top + rect.height / 2 })
|
|
30
|
-
}
|
|
31
|
-
setShow(true)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div
|
|
36
|
-
ref={ref}
|
|
37
|
-
className="inline-flex"
|
|
38
|
-
onMouseEnter={handleMouseEnter}
|
|
39
|
-
onMouseLeave={() => setShow(false)}
|
|
40
|
-
>
|
|
41
|
-
{children}
|
|
42
|
-
{show && createPortal(
|
|
43
|
-
<div
|
|
44
|
-
className="fixed z-[9999] pointer-events-none"
|
|
45
|
-
style={{ left: pos.x, top: pos.y, transform: 'translateY(-50%)' }}
|
|
46
|
-
>
|
|
47
|
-
<div className="bg-gray-900 text-white text-[10px] px-2 py-1.5 rounded shadow-lg max-w-[180px] leading-tight whitespace-normal">
|
|
48
|
-
{content}
|
|
49
|
-
</div>
|
|
50
|
-
</div>,
|
|
51
|
-
document.body
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
18
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
56
19
|
|
|
57
20
|
// Connection threshold options
|
|
58
21
|
const CONNECTION_THRESHOLDS = [
|
|
@@ -152,7 +115,7 @@ function ToggleOption({
|
|
|
152
115
|
{label}
|
|
153
116
|
</span>
|
|
154
117
|
</button>
|
|
155
|
-
<Tooltip content={description}>
|
|
118
|
+
<Tooltip content={description} position="right">
|
|
156
119
|
<Info className="w-3 h-3 text-theme-text-tertiary hover:text-theme-text-secondary cursor-help" />
|
|
157
120
|
</Tooltip>
|
|
158
121
|
<button
|
|
@@ -225,29 +188,31 @@ export const TrafficFilterSidebar = memo(function TrafficFilterSidebar({
|
|
|
225
188
|
<div className="px-3 py-2 border-b border-theme-border space-y-1.5">
|
|
226
189
|
<div className="flex items-center gap-2">
|
|
227
190
|
<Clock className="w-3.5 h-3.5 text-theme-text-tertiary" />
|
|
191
|
+
<Tooltip content="Show traffic from the selected time window" wrapperClassName="!flex flex-1">
|
|
228
192
|
<select
|
|
229
193
|
value={timeRange}
|
|
230
194
|
onChange={(e) => setTimeRange(e.target.value)}
|
|
231
|
-
title="Show traffic from the selected time window"
|
|
232
195
|
className="flex-1 bg-theme-elevated text-theme-text-primary text-xs rounded px-2 py-1.5 border border-theme-border focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
233
196
|
>
|
|
234
197
|
{TIME_RANGES.map(({ value, label }) => (
|
|
235
198
|
<option key={value} value={value}>{label}</option>
|
|
236
199
|
))}
|
|
237
200
|
</select>
|
|
201
|
+
</Tooltip>
|
|
238
202
|
</div>
|
|
239
203
|
<div className="flex items-center gap-2">
|
|
240
204
|
<Filter className="w-3.5 h-3.5 text-theme-text-tertiary" />
|
|
205
|
+
<Tooltip content="Hide low-traffic flows to reduce noise" wrapperClassName="!flex flex-1">
|
|
241
206
|
<select
|
|
242
207
|
value={minConnections}
|
|
243
208
|
onChange={(e) => setMinConnections(Number(e.target.value))}
|
|
244
|
-
title="Hide low-traffic flows to reduce noise"
|
|
245
209
|
className="flex-1 bg-theme-elevated text-theme-text-primary text-xs rounded px-2 py-1.5 border border-theme-border focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
246
210
|
>
|
|
247
211
|
{CONNECTION_THRESHOLDS.map(({ value, label }) => (
|
|
248
212
|
<option key={value} value={value}>{label}</option>
|
|
249
213
|
))}
|
|
250
214
|
</select>
|
|
215
|
+
</Tooltip>
|
|
251
216
|
</div>
|
|
252
217
|
</div>
|
|
253
218
|
|
|
@@ -279,7 +244,7 @@ export const TrafficFilterSidebar = memo(function TrafficFilterSidebar({
|
|
|
279
244
|
<div className="flex items-center gap-2 mb-1.5">
|
|
280
245
|
<Puzzle className="w-3.5 h-3.5 text-theme-text-tertiary" />
|
|
281
246
|
<span className="text-xs text-theme-text-primary">Cluster Addons</span>
|
|
282
|
-
<Tooltip content="Monitoring, logging, cert-manager, etc. Excludes ingress controllers and service mesh.">
|
|
247
|
+
<Tooltip content="Monitoring, logging, cert-manager, etc. Excludes ingress controllers and service mesh." position="right">
|
|
283
248
|
<Info className="w-3 h-3 text-theme-text-tertiary hover:text-theme-text-secondary cursor-help" />
|
|
284
249
|
</Tooltip>
|
|
285
250
|
</div>
|
|
@@ -8,6 +8,7 @@ import { pluralize } from '@skyhook-io/k8s-ui'
|
|
|
8
8
|
import { useFlowSearch } from './TrafficFlowListContext'
|
|
9
9
|
import { useQuery } from '@tanstack/react-query'
|
|
10
10
|
import { fetchJSON } from '../../api/client'
|
|
11
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
11
12
|
|
|
12
13
|
// DNS response code names
|
|
13
14
|
const DNS_RCODES: Record<number, string> = {
|
|
@@ -174,28 +175,36 @@ export function TrafficFlowList({ flows }: TrafficFlowListProps) {
|
|
|
174
175
|
<span className="text-theme-text-tertiary tabular-nums whitespace-nowrap">{time}</span>
|
|
175
176
|
|
|
176
177
|
{/* Source */}
|
|
177
|
-
<
|
|
178
|
+
<Tooltip content={flow.source.namespace ? `${flow.source.namespace}/${flow.source.name}` : flow.source.name} wrapperClassName="min-w-0">
|
|
179
|
+
<span className="truncate text-theme-text-primary">
|
|
178
180
|
{flow.source.name}
|
|
179
181
|
</span>
|
|
182
|
+
</Tooltip>
|
|
180
183
|
|
|
181
184
|
{/* Destination */}
|
|
182
|
-
<
|
|
185
|
+
<Tooltip content={flow.destination.namespace ? `${flow.destination.namespace}/${flow.destination.name}` : flow.destination.name} wrapperClassName="min-w-0">
|
|
186
|
+
<span className="truncate text-theme-text-primary">
|
|
183
187
|
{flow.destination.name}
|
|
184
188
|
</span>
|
|
189
|
+
</Tooltip>
|
|
185
190
|
|
|
186
191
|
{/* Request info */}
|
|
187
192
|
<div className="flex items-center gap-1.5 min-w-0">
|
|
188
193
|
{isHTTP && (
|
|
189
194
|
<>
|
|
190
195
|
<span className={clsx('shrink-0 badge badge-sm text-[10px]', SEVERITY_BADGE.info)}>{flow.httpMethod}</span>
|
|
191
|
-
<
|
|
196
|
+
<Tooltip content={flow.httpPath ?? ''} wrapperClassName="min-w-0">
|
|
197
|
+
<span className="truncate text-theme-text-secondary">{flow.httpPath}</span>
|
|
198
|
+
</Tooltip>
|
|
192
199
|
{flow.l7Type === 'REQUEST' && <span className={clsx('shrink-0 text-[9px]', SEVERITY_TEXT.warning)}>no response</span>}
|
|
193
200
|
</>
|
|
194
201
|
)}
|
|
195
202
|
{isDNS && (
|
|
196
203
|
<>
|
|
197
204
|
<span className={clsx('shrink-0 badge badge-sm text-[10px]', SEVERITY_BADGE.neutral)}>DNS</span>
|
|
198
|
-
<
|
|
205
|
+
<Tooltip content={flow.dnsQuery ?? ''} wrapperClassName="min-w-0">
|
|
206
|
+
<span className="truncate text-theme-text-secondary">{flow.dnsQuery}</span>
|
|
207
|
+
</Tooltip>
|
|
199
208
|
</>
|
|
200
209
|
)}
|
|
201
210
|
{!isHTTP && !isDNS && (
|
|
@@ -22,6 +22,7 @@ import { X, ArrowRight, Globe, Server, Activity, Puzzle } from 'lucide-react'
|
|
|
22
22
|
import { isClusterAddon, type AddonMode } from './TrafficView'
|
|
23
23
|
import { SEVERITY_BADGE, SEVERITY_TEXT } from '@skyhook-io/k8s-ui/utils/badge-colors'
|
|
24
24
|
import { getNamespaceColor } from '../../utils/traffic-colors'
|
|
25
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
25
26
|
|
|
26
27
|
const elk = new ELK()
|
|
27
28
|
|
|
@@ -845,7 +846,9 @@ function DetailsPanel({
|
|
|
845
846
|
{edgeData.flow.topHTTPPaths.map((p, i) => (
|
|
846
847
|
<div key={i} className="flex items-center gap-1.5 text-[10px]">
|
|
847
848
|
<span className={clsx('shrink-0 px-1 py-0.5 rounded badge font-medium', SEVERITY_BADGE.info)}>{p.method}</span>
|
|
848
|
-
<
|
|
849
|
+
<Tooltip content={p.path} wrapperClassName="min-w-0 flex-1">
|
|
850
|
+
<span className="text-theme-text-primary truncate flex-1">{p.path || '/'}</span>
|
|
851
|
+
</Tooltip>
|
|
849
852
|
<span className="shrink-0 text-theme-text-secondary">{p.count}</span>
|
|
850
853
|
{p.avgMs ? <span className="shrink-0 text-theme-text-tertiary">{formatLatency(p.avgMs)}</span> : null}
|
|
851
854
|
{p.errorPct ? <span className={clsx('shrink-0', p.errorPct > 10 ? SEVERITY_TEXT.error : SEVERITY_TEXT.warning)}>{p.errorPct.toFixed(0)}%err</span> : null}
|
|
@@ -862,7 +865,9 @@ function DetailsPanel({
|
|
|
862
865
|
<div className="space-y-1 max-h-40 overflow-y-auto">
|
|
863
866
|
{edgeData.flow.topDNSQueries.map((q, i) => (
|
|
864
867
|
<div key={i} className="flex items-center gap-1.5 text-[10px]">
|
|
865
|
-
<
|
|
868
|
+
<Tooltip content={q.query} wrapperClassName="min-w-0 flex-1">
|
|
869
|
+
<span className="text-theme-text-primary truncate flex-1">{q.query}</span>
|
|
870
|
+
</Tooltip>
|
|
866
871
|
<span className="shrink-0 text-theme-text-secondary">{q.count}</span>
|
|
867
872
|
{q.nxCount ? <span className={clsx('shrink-0', SEVERITY_TEXT.warning)}>NX:{q.nxCount}</span> : null}
|
|
868
873
|
{q.avgTTL ? <span className="shrink-0 text-theme-text-tertiary">TTL:{q.avgTTL}s</span> : null}
|
|
@@ -1343,7 +1348,7 @@ export function TrafficGraph({ flows, hotPathThreshold = 0, showNamespaceGroups
|
|
|
1343
1348
|
}
|
|
1344
1349
|
|
|
1345
1350
|
// Build final edges list
|
|
1346
|
-
|
|
1351
|
+
const finalEdges = [...rawEdges]
|
|
1347
1352
|
|
|
1348
1353
|
// Add edge from addon-internet to addon-group if we have one
|
|
1349
1354
|
if (addonMode === 'group' && groupEdgeInfo) {
|
|
@@ -12,6 +12,7 @@ import { clsx } from 'clsx'
|
|
|
12
12
|
import { useQueryClient } from '@tanstack/react-query'
|
|
13
13
|
import { useDock } from '../dock'
|
|
14
14
|
import { EmptyState, PaneLoader } from '@skyhook-io/k8s-ui'
|
|
15
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
15
16
|
|
|
16
17
|
// Addon types for filtering
|
|
17
18
|
export type AddonMode = 'show' | 'group' | 'hide'
|
|
@@ -1117,11 +1118,12 @@ export function TrafficView({ namespaces }: TrafficViewProps) {
|
|
|
1117
1118
|
{/* Top-right: stats + actions */}
|
|
1118
1119
|
<div className="absolute top-3 right-3 z-10 flex items-center gap-2">
|
|
1119
1120
|
{flowsData?.flows && flowsData.flows.length > 0 && (
|
|
1121
|
+
<Tooltip content="Open flow list in dock">
|
|
1120
1122
|
<button onClick={openFlowListDock}
|
|
1121
|
-
className="flex items-center gap-1 px-2 py-1 text-[10px] rounded-lg bg-theme-surface/90 backdrop-blur border border-theme-border text-theme-text-secondary hover:text-theme-text-primary transition-colors"
|
|
1122
|
-
title="Open flow list in dock">
|
|
1123
|
+
className="flex items-center gap-1 px-2 py-1 text-[10px] rounded-lg bg-theme-surface/90 backdrop-blur border border-theme-border text-theme-text-secondary hover:text-theme-text-primary transition-colors">
|
|
1123
1124
|
<List className="w-3 h-3" /> Flows
|
|
1124
1125
|
</button>
|
|
1126
|
+
</Tooltip>
|
|
1125
1127
|
)}
|
|
1126
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">
|
|
1127
1129
|
{flowStats.shown}/{flowStats.total}
|
|
@@ -283,7 +283,6 @@ export const Omnibar = forwardRef<OmnibarHandle, OmnibarProps>(function Omnibar(
|
|
|
283
283
|
out.push({ id: 'view-all', kind: 'viewAll', query: queryString, count: searchData?.total_matched ?? resourceRows.length })
|
|
284
284
|
}
|
|
285
285
|
return out
|
|
286
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
287
286
|
}, [recentRows, leadingKinds, resourceRows, commandGroups, freeText, pills.length, searchActive, onViewAllResults, queryString, searchData])
|
|
288
287
|
const viewAllRow = rows.find((r): r is Extract<Row, { kind: 'viewAll' }> => r.kind === 'viewAll')
|
|
289
288
|
|
|
@@ -43,6 +43,7 @@ import { useCanUpdateSecrets, useCanNodeWrite, useNamespacedCapabilities } from
|
|
|
43
43
|
import { useOpenTerminal, useOpenLogs, useOpenWorkloadLogs, useOpenNodeTerminal } from '../dock'
|
|
44
44
|
import { PortForwardButton } from '../portforward/PortForwardButton'
|
|
45
45
|
import { useToast } from '../ui/Toast'
|
|
46
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
46
47
|
import { PodRenderer } from '../resources/renderers/PodRenderer'
|
|
47
48
|
import { NodeRenderer } from '../resources/renderers/NodeRenderer'
|
|
48
49
|
import { ServiceRenderer } from '../resources/renderers/ServiceRenderer'
|
|
@@ -98,14 +99,6 @@ export function WorkloadViewRoute({ onNavigateToResource }: WorkloadViewRoutePro
|
|
|
98
99
|
const name = parts.slice(3).map(decode).join('/')
|
|
99
100
|
const group = searchParams.get('apiGroup') || ''
|
|
100
101
|
|
|
101
|
-
if (!kind || !namespace || !name) {
|
|
102
|
-
return (
|
|
103
|
-
<div className="flex items-center justify-center h-full text-theme-text-tertiary">
|
|
104
|
-
Invalid workload URL
|
|
105
|
-
</div>
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
102
|
const handleBack = useCallback(() => {
|
|
110
103
|
if (window.history.length > 1) {
|
|
111
104
|
navigate(-1)
|
|
@@ -118,6 +111,15 @@ export function WorkloadViewRoute({ onNavigateToResource }: WorkloadViewRoutePro
|
|
|
118
111
|
navigate(buildWorkloadPath(resource))
|
|
119
112
|
}, [navigate])
|
|
120
113
|
|
|
114
|
+
// Hooks must run unconditionally — the invalid-URL guard comes after them.
|
|
115
|
+
if (!kind || !namespace || !name) {
|
|
116
|
+
return (
|
|
117
|
+
<div className="flex items-center justify-center h-full text-theme-text-tertiary">
|
|
118
|
+
Invalid workload URL
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
121
123
|
return (
|
|
122
124
|
<WorkloadView
|
|
123
125
|
kind={kind}
|
|
@@ -879,15 +881,18 @@ function FluxSourceConsumersInner({ sourceKind, namespace, name }: { sourceKind:
|
|
|
879
881
|
</h3>
|
|
880
882
|
<div className="flex flex-wrap gap-1.5">
|
|
881
883
|
{consumers.map((c) => (
|
|
882
|
-
<
|
|
884
|
+
<Tooltip
|
|
883
885
|
key={`${c.kind}/${c.namespace}/${c.name}`}
|
|
886
|
+
content={`${c.kind} ${c.namespace}/${c.name}`}
|
|
887
|
+
>
|
|
888
|
+
<button
|
|
884
889
|
onClick={() => navigate(`/gitops/detail/${c.plural}/${encodeURIComponent(c.namespace)}/${encodeURIComponent(c.name)}`)}
|
|
885
890
|
className="inline-flex items-center gap-1.5 rounded border border-theme-border bg-theme-surface px-1.5 py-0.5 text-[11px] text-theme-text-secondary hover:border-skyhook-500/60 hover:text-skyhook-500 transition-colors"
|
|
886
|
-
title={`${c.kind} ${c.namespace}/${c.name}`}
|
|
887
891
|
>
|
|
888
892
|
<span className="text-theme-text-tertiary">{c.kind === 'HelmRelease' ? 'HR' : 'K'}</span>
|
|
889
893
|
<span>{c.namespace}/{c.name}</span>
|
|
890
894
|
</button>
|
|
895
|
+
</Tooltip>
|
|
891
896
|
))}
|
|
892
897
|
</div>
|
|
893
898
|
</div>
|
|
@@ -54,11 +54,14 @@ interface NavCustomizationBase {
|
|
|
54
54
|
* `clusterChecksHref` folded in here)
|
|
55
55
|
* - 'certs' → the Certificate Health card
|
|
56
56
|
*
|
|
57
|
-
* View-shaped targets (issues / gitops / checks) are
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
57
|
+
* View-shaped targets (issues / gitops / checks) are honored for every entry:
|
|
58
|
+
* in-app nav (Home cards, ⌘K, "view all") hands straight to the host from
|
|
59
|
+
* `setMainView` via `onHostNavigate` (smooth same-document hand-off, no
|
|
60
|
+
* intermediate /<view> mount); a direct /<view> URL (bookmark/deep link)
|
|
61
|
+
* funnels through a redirect effect that uses `window.location.replace` so
|
|
62
|
+
* the transient URL stays out of history. 'certs' has no Radar view, so only
|
|
63
|
+
* the card consults it. `onHostNavigate` is optional — without it everything
|
|
64
|
+
* falls back to `window.location` (a hard reload).
|
|
62
65
|
*/
|
|
63
66
|
fleetTakeoverHref?: (target: FleetTakeoverTarget) => string | undefined;
|
|
64
67
|
/**
|
|
@@ -68,6 +71,17 @@ interface NavCustomizationBase {
|
|
|
68
71
|
* change. Remove in a major release once all consumers have migrated.
|
|
69
72
|
*/
|
|
70
73
|
clusterChecksHref?: () => string;
|
|
74
|
+
/**
|
|
75
|
+
* Optional smooth navigator for host-owned URLs. When the host takes a
|
|
76
|
+
* destination over (`fleetTakeoverHref`, `crossClusterCompareHref`), Radar
|
|
77
|
+
* would otherwise hand off via `window.location` — a full document reload
|
|
78
|
+
* that cold-boots the host (white flash, re-auth, chrome teardown). A host
|
|
79
|
+
* that can navigate SAME-DOCUMENT (e.g. Radar Cloud's cross-tree swap with a
|
|
80
|
+
* View Transition) passes this so the hand-off morphs instead of reloading.
|
|
81
|
+
* Omitted → Radar falls back to `window.location` (hard nav), so standalone
|
|
82
|
+
* OSS / other hosts are unaffected.
|
|
83
|
+
*/
|
|
84
|
+
onHostNavigate?: (url: string) => void;
|
|
71
85
|
/**
|
|
72
86
|
* Chrome level for embedded hosts. Default ('full', or omitted) renders
|
|
73
87
|
* Radar's top bar + the view-switcher. 'none' suppresses BOTH — the host
|