@skyhook-io/radar-app 1.4.2 → 1.6.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 +10 -11
- package/src/App.tsx +168 -42
- package/src/RadarApp.tsx +9 -1
- package/src/api/client.ts +185 -6
- package/src/components/UserMenu.tsx +56 -10
- package/src/components/applications/ApplicationsView.tsx +27 -19
- package/src/components/audit/AuditSettingsDialog.tsx +1 -1
- package/src/components/audit/AuditView.tsx +23 -35
- package/src/components/gitops/GitOpsView.tsx +24 -2
- package/src/components/helm/HelmView.tsx +12 -8
- package/src/components/home/HomeView.tsx +1 -1
- package/src/components/home/mcpToolCatalog.ts +34 -0
- package/src/components/issues/IssuesPane.tsx +82 -28
- package/src/components/nav/PrimaryNavRail.tsx +282 -0
- package/src/components/resource/HPACharts.tsx +7 -2
- package/src/components/resource/RestartChart.tsx +8 -0
- package/src/components/resources/ResourcesView.tsx +54 -3
- package/src/components/resources/renderers/HPARenderer.tsx +4 -1
- package/src/components/resources/renderers/WorkloadRenderer.tsx +34 -3
- package/src/components/settings/SettingsDialog.tsx +44 -7
- package/src/components/ui/CommandPalette.tsx +6 -215
- package/src/components/ui/Omnibar.tsx +493 -0
- package/src/components/ui/SearchSyntaxHelp.tsx +89 -0
- package/src/components/ui/command-items.ts +178 -0
- package/src/components/workload/WorkloadView.tsx +3 -1
- package/src/context/NavCustomization.tsx +11 -0
- package/src/contexts/CapabilitiesContext.tsx +16 -5
- package/src/hooks/useMediaQuery.ts +21 -0
- package/src/hooks/useNavRailPinned.ts +46 -0
- package/src/hooks/useRecentResources.ts +49 -0
- package/src/utils/navigation.ts +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyhook-io/radar-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Radar's full web UI as a reusable React component. Used by Radar's own binary and by external consumers like Radar Cloud.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"react-markdown": "^10.1.0",
|
|
36
36
|
"react-virtuoso": "^4.18.7",
|
|
37
37
|
"remark-gfm": "^4.0.1",
|
|
38
|
-
"shiki": "^4.0
|
|
38
|
+
"shiki": "^4.2.0",
|
|
39
39
|
"yaml": "^2.9.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
@@ -53,12 +53,11 @@
|
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@playwright/test": "^1.59.1",
|
|
55
55
|
"@skyhook-io/k8s-ui": "*",
|
|
56
|
-
"@tailwindcss/typography": "^0.5.
|
|
57
|
-
"@tailwindcss/vite": "^4.3.
|
|
56
|
+
"@tailwindcss/typography": "^0.5.20",
|
|
57
|
+
"@tailwindcss/vite": "^4.3.1",
|
|
58
58
|
"@tanstack/react-query": "^5.100.14",
|
|
59
|
-
"@types/
|
|
60
|
-
"@types/
|
|
61
|
-
"@types/react": "^19.2.14",
|
|
59
|
+
"@types/node": "^25.9.3",
|
|
60
|
+
"@types/react": "^19.2.17",
|
|
62
61
|
"@types/react-dom": "^19.2.3",
|
|
63
62
|
"@vitejs/plugin-react": "^6.0.2",
|
|
64
63
|
"@xyflow/react": "^12.10.2",
|
|
@@ -67,11 +66,11 @@
|
|
|
67
66
|
"lucide-react": "^1.16.0",
|
|
68
67
|
"postcss": "^8.5.14",
|
|
69
68
|
"prettier": "^3.8.1",
|
|
70
|
-
"react": "^19.2.
|
|
71
|
-
"react-dom": "^19.2.
|
|
72
|
-
"react-router-dom": "^7.
|
|
69
|
+
"react": "^19.2.7",
|
|
70
|
+
"react-dom": "^19.2.7",
|
|
71
|
+
"react-router-dom": "^7.17.0",
|
|
73
72
|
"tailwind-merge": "^3.6.0",
|
|
74
|
-
"tailwindcss": "^4.
|
|
73
|
+
"tailwindcss": "^4.3.1",
|
|
75
74
|
"typescript": "^6.0.2",
|
|
76
75
|
"vite": "^8.0.12"
|
|
77
76
|
},
|
package/src/App.tsx
CHANGED
|
@@ -7,6 +7,8 @@ import { useNavigate, useLocation, useSearchParams, useNavigationType, Navigatio
|
|
|
7
7
|
import { HomeView } from './components/home/HomeView'
|
|
8
8
|
import { DebugOverlay } from './components/DebugOverlay'
|
|
9
9
|
import { TopologyGraph, TopologySearch, TopologyFilterSidebar, TopologyControls, gitOpsRouteForKind, gitOpsRouteForResource } from '@skyhook-io/k8s-ui'
|
|
10
|
+
import { initNavigationMap } from '@skyhook-io/k8s-ui/utils/navigation'
|
|
11
|
+
import { useAPIResources } from './api/apiResources'
|
|
10
12
|
import { TimelineView } from './components/timeline/TimelineView'
|
|
11
13
|
import { ResourcesView } from './components/resources/ResourcesView'
|
|
12
14
|
import { serializeColumnFilters } from './components/resources/resource-utils'
|
|
@@ -27,6 +29,9 @@ import { DURATION_DOCK } from '@skyhook-io/k8s-ui/utils/animation'
|
|
|
27
29
|
import { ContextSwitcher } from './components/ContextSwitcher'
|
|
28
30
|
import { NamespaceSwitcher, type NamespaceSwitcherHandle } from './components/NamespaceSwitcher'
|
|
29
31
|
import { useNavCustomization } from './context/NavCustomization'
|
|
32
|
+
import { PrimaryNavRail } from './components/nav/PrimaryNavRail'
|
|
33
|
+
import { useNavRailPinned } from './hooks/useNavRailPinned'
|
|
34
|
+
import { useMediaQuery } from './hooks/useMediaQuery'
|
|
30
35
|
import { ContextSwitchProvider, useContextSwitch } from './context/ContextSwitchContext'
|
|
31
36
|
import { ConnectionProvider, useConnection } from './context/ConnectionContext'
|
|
32
37
|
import { ConnectionErrorView } from './components/ConnectionErrorView'
|
|
@@ -43,14 +48,15 @@ import { routePath, apiUrl, getAuthHeaders, getCredentialsMode } from './api/con
|
|
|
43
48
|
import { KeyboardShortcutProvider, useRegisterShortcut, useRegisterShortcuts } from './hooks/useKeyboardShortcuts'
|
|
44
49
|
import { useAnimatedUnmount } from './hooks/useAnimatedUnmount'
|
|
45
50
|
import radarLoadingIcon from '@skyhook-io/k8s-ui/assets/radar/radar-icon-loading.svg'
|
|
46
|
-
import { RefreshCw, Network, List, Clock, Package, Sun, Moon, Activity, Home, Star, Search, Bug,
|
|
51
|
+
import { RefreshCw, Network, List, Clock, Package, Sun, Moon, Activity, Home, Star, Search, Bug, SquareTerminal, ShieldCheck, GitBranch, HelpCircle } from 'lucide-react'
|
|
47
52
|
import { useTheme } from './context/ThemeContext'
|
|
48
53
|
import { Tooltip } from './components/ui/Tooltip'
|
|
49
54
|
import { LargeClusterNamespacePicker } from './components/shared/LargeClusterNamespacePicker'
|
|
50
55
|
import { SettingsDialog } from './components/settings/SettingsDialog'
|
|
51
56
|
import { MyPermissionsDialog } from './components/settings/MyPermissionsDialog'
|
|
52
57
|
import type { TopologyNode, GroupingMode, MainView, SelectedResource, SelectedHelmRelease, NodeKind, TopologyMode, Topology, K8sEvent } from './types'
|
|
53
|
-
import { kindToPlural, openExternal, apiVersionToGroup, buildWorkloadPath } from './utils/navigation'
|
|
58
|
+
import { kindToPlural, openExternal, apiVersionToGroup, buildWorkloadPath, searchHitToSelectedResource } from './utils/navigation'
|
|
59
|
+
import { Omnibar, type OmnibarHandle } from './components/ui/Omnibar'
|
|
54
60
|
import type { ContextSwitcherHandle } from './components/ContextSwitcher'
|
|
55
61
|
|
|
56
62
|
// All possible node kinds (core + GitOps)
|
|
@@ -94,7 +100,7 @@ const FLEET_MODE_KINDS = new Set<NodeKind>([
|
|
|
94
100
|
|
|
95
101
|
// Convert API resource name back to topology node ID prefix
|
|
96
102
|
// Extended MainView type that includes traffic and cost
|
|
97
|
-
type ExtendedMainView = MainView | 'traffic' | 'cost' | 'workload' | '
|
|
103
|
+
type ExtendedMainView = MainView | 'traffic' | 'cost' | 'workload' | 'checks' | 'gitops' | 'compare' | 'issues' | 'applications'
|
|
98
104
|
|
|
99
105
|
// Extract view from URL path
|
|
100
106
|
function getViewFromPath(pathname: string): ExtendedMainView {
|
|
@@ -107,7 +113,7 @@ function getViewFromPath(pathname: string): ExtendedMainView {
|
|
|
107
113
|
if (path === 'traffic') return 'traffic'
|
|
108
114
|
if (path === 'cost') return 'cost'
|
|
109
115
|
if (path === 'workload') return 'workload'
|
|
110
|
-
if (path === 'audit') return 'audit
|
|
116
|
+
if (path === 'checks' || path === 'audit') return 'checks' // /audit = legacy → checks
|
|
111
117
|
if (path === 'gitops') return 'gitops'
|
|
112
118
|
if (path === 'applications') return 'applications'
|
|
113
119
|
if (path === 'compare') return 'compare'
|
|
@@ -171,6 +177,24 @@ function AppInner() {
|
|
|
171
177
|
const capabilities = useCapabilitiesContext()
|
|
172
178
|
const openLocalTerminal = useOpenLocalTerminal()
|
|
173
179
|
const navCustomization = useNavCustomization()
|
|
180
|
+
const { pinned: navRailPinned, togglePinned: toggleNavRailPinned } = useNavRailPinned()
|
|
181
|
+
// Standalone Radar gets the left nav rail; embedded hosts (Radar Hub) own
|
|
182
|
+
// the left chrome via their own fleet rail and keep Radar's top-bar pills.
|
|
183
|
+
const showNavRail = !navCustomization.embedded
|
|
184
|
+
// Chromeless embed: the host (Radar Hub) owns ALL chrome and drives view
|
|
185
|
+
// navigation + scope from its own UI, so Radar renders just the active view's
|
|
186
|
+
// content — no top bar, no view-switcher. Used for per-cluster views surfaced
|
|
187
|
+
// as native cloud destinations behind a cluster picker.
|
|
188
|
+
const chromeless = navCustomization.embedded === true && navCustomization.chrome === 'none'
|
|
189
|
+
// Force the slim rail on narrow windows: a pinned 176px rail needs viewport
|
|
190
|
+
// ≥976 to keep content above its ~800px floor (collapsed needs only ≥856).
|
|
191
|
+
// Below 976 we render collapsed regardless of the pin preference — a
|
|
192
|
+
// temporary responsive override that does NOT touch the persisted value, so
|
|
193
|
+
// the user's pinned state returns when they widen again. Fly-out labels cover
|
|
194
|
+
// the collapsed state, so the manual toggle is hidden here rather than left
|
|
195
|
+
// inert (expanding would just re-breach the floor).
|
|
196
|
+
const railForcedSlim = useMediaQuery('(max-width: 975px)')
|
|
197
|
+
const navRailEffectivePinned = navRailPinned && !railForcedSlim
|
|
174
198
|
|
|
175
199
|
// Auth check — detect if auth is enabled but user is not authenticated
|
|
176
200
|
const { data: authMe, isPending: authMePending } = useAuthMe()
|
|
@@ -262,7 +286,7 @@ function AppInner() {
|
|
|
262
286
|
// unaffected and renders the in-app audit view as before.
|
|
263
287
|
const clusterChecksHref = navCustomization.clusterChecksHref
|
|
264
288
|
useEffect(() => {
|
|
265
|
-
if (clusterChecksHref && mainView === '
|
|
289
|
+
if (clusterChecksHref && mainView === 'checks') {
|
|
266
290
|
window.location.replace(clusterChecksHref())
|
|
267
291
|
}
|
|
268
292
|
}, [clusterChecksHref, mainView])
|
|
@@ -450,14 +474,38 @@ function AppInner() {
|
|
|
450
474
|
|
|
451
475
|
// Refs for dropdown components to trigger them via shortcuts
|
|
452
476
|
const namespaceSwitcherRef = useRef<NamespaceSwitcherHandle>(null)
|
|
477
|
+
const omnibarRef = useRef<OmnibarHandle>(null)
|
|
478
|
+
|
|
479
|
+
// Initialize the kind→plural discovery map app-wide (not just on ResourcesView
|
|
480
|
+
// mount) so the omnibar can open a CRD hit with an irregular plural from any
|
|
481
|
+
// view — kindToPlural would otherwise English-guess the route before a
|
|
482
|
+
// resources view has run n().
|
|
483
|
+
const { data: navApiResources } = useAPIResources()
|
|
484
|
+
useEffect(() => { if (navApiResources) initNavigationMap(navApiResources) }, [navApiResources])
|
|
453
485
|
const contextSwitcherRef = useRef<ContextSwitcherHandle>(null)
|
|
454
486
|
|
|
455
487
|
// View switching keyboard shortcuts
|
|
456
|
-
|
|
488
|
+
// `g`+mnemonic sequences cover every view. Numeric 1–N can't: there are 11
|
|
489
|
+
// views and only 9 single digits, so `10`/`11` never match a keypress (a
|
|
490
|
+
// KeyboardEvent.key is one character). `g`-prefixed mnemonics scale, are the
|
|
491
|
+
// GitHub/Linear convention, and their second keys are all distinct (no clash
|
|
492
|
+
// with the scoped `g g` table shortcut). The letters are fixed regardless of
|
|
493
|
+
// position, so reordering the rail never changes a shortcut.
|
|
494
|
+
const VIEW_SHORTCUT_KEYS: Record<ExtendedMainView, string> = {
|
|
495
|
+
home: 'g h', resources: 'g r', issues: 'g i', topology: 'g t',
|
|
496
|
+
applications: 'g a', timeline: 'g l', traffic: 'g f', helm: 'g m',
|
|
497
|
+
gitops: 'g o', checks: 'g u', cost: 'g c',
|
|
498
|
+
// Non-rail views (reachable via deep links / actions, not the rail) get no
|
|
499
|
+
// dedicated mnemonic — listed for exhaustiveness so the type stays total.
|
|
500
|
+
workload: '', compare: '',
|
|
501
|
+
}
|
|
502
|
+
const views = Object.keys(VIEW_SHORTCUT_KEYS).filter(
|
|
503
|
+
(v): v is ExtendedMainView => VIEW_SHORTCUT_KEYS[v as ExtendedMainView] !== '',
|
|
504
|
+
)
|
|
457
505
|
useRegisterShortcuts([
|
|
458
|
-
...views.map((view
|
|
506
|
+
...views.map((view) => ({
|
|
459
507
|
id: `view-${view}`,
|
|
460
|
-
keys:
|
|
508
|
+
keys: VIEW_SHORTCUT_KEYS[view],
|
|
461
509
|
description: `Go to ${view.charAt(0).toUpperCase() + view.slice(1)}`,
|
|
462
510
|
category: 'Navigation' as const,
|
|
463
511
|
scope: 'global' as const,
|
|
@@ -498,11 +546,12 @@ function AppInner() {
|
|
|
498
546
|
{
|
|
499
547
|
id: 'command-palette',
|
|
500
548
|
keys: 'Cmd+k',
|
|
501
|
-
description: '
|
|
549
|
+
description: 'Search resources & commands',
|
|
502
550
|
category: 'General' as const,
|
|
503
551
|
scope: 'global' as const,
|
|
504
552
|
allowInInputs: true,
|
|
505
|
-
|
|
553
|
+
// Standalone focuses the top-center omnibar; embedded opens the modal.
|
|
554
|
+
handler: () => { if (showNavRail) omnibarRef.current?.focus(); else setShowCommandPalette(true) },
|
|
506
555
|
},
|
|
507
556
|
{
|
|
508
557
|
id: 'diagnostics',
|
|
@@ -513,6 +562,20 @@ function AppInner() {
|
|
|
513
562
|
allowInInputs: true,
|
|
514
563
|
handler: () => setShowDiagnostics(prev => !prev),
|
|
515
564
|
},
|
|
565
|
+
// Settings exposes local-binary controls that don't apply to embedded hosts.
|
|
566
|
+
// Register the shortcut only when standalone (matching the gear button) —
|
|
567
|
+
// `enabled: false` would still list it in the `?` help overlay, which shows
|
|
568
|
+
// all registered shortcuts regardless of enabled state.
|
|
569
|
+
...(showNavRail
|
|
570
|
+
? [{
|
|
571
|
+
id: 'open-settings',
|
|
572
|
+
keys: 'g s',
|
|
573
|
+
description: 'Open settings',
|
|
574
|
+
category: 'General' as const,
|
|
575
|
+
scope: 'global' as const,
|
|
576
|
+
handler: () => setShowSettings(true),
|
|
577
|
+
}]
|
|
578
|
+
: []),
|
|
516
579
|
])
|
|
517
580
|
|
|
518
581
|
// Separate registration for help-close — its `enabled` changes with showHelp,
|
|
@@ -1141,12 +1204,38 @@ function AppInner() {
|
|
|
1141
1204
|
|
|
1142
1205
|
return (
|
|
1143
1206
|
<PortForwardProvider>
|
|
1144
|
-
|
|
1145
|
-
|
|
1207
|
+
{/* Preserve the ~800px content floor: the rail is a fixed-width sibling, so
|
|
1208
|
+
the outer minimum must include it (176px pinned / 56px collapsed) or the
|
|
1209
|
+
content column (min-w-0, shrinkable) would fall below the old desktop
|
|
1210
|
+
floor at small windows. Embedded mode has no rail → plain 800. */}
|
|
1211
|
+
<div
|
|
1212
|
+
className="relative flex h-screen bg-theme-base"
|
|
1213
|
+
style={{ minWidth: 800 + (showNavRail ? (navRailEffectivePinned ? 176 : 56) : 0) }}
|
|
1214
|
+
>
|
|
1215
|
+
{showNavRail && (
|
|
1216
|
+
<PrimaryNavRail
|
|
1217
|
+
activeView={mainView}
|
|
1218
|
+
onNavigate={setMainView}
|
|
1219
|
+
pinned={navRailEffectivePinned}
|
|
1220
|
+
onTogglePinned={toggleNavRailPinned}
|
|
1221
|
+
showPinToggle={!railForcedSlim}
|
|
1222
|
+
onOpenSettings={() => setShowSettings(true)}
|
|
1223
|
+
accountSlot={<UserMenu variant="rail" pinned={navRailEffectivePinned} />}
|
|
1224
|
+
/>
|
|
1225
|
+
)}
|
|
1226
|
+
{/* `relative` makes this column the containing block for the absolute
|
|
1227
|
+
overlays it hosts (BottomDock, expanded ResourceDetailDrawer) so they
|
|
1228
|
+
span the content area AFTER the rail rather than the full viewport
|
|
1229
|
+
under it. `fixed` splashes (connecting/switching) are unaffected. */}
|
|
1230
|
+
<div className="relative flex flex-col flex-1 min-w-0 h-full">
|
|
1231
|
+
{/* Header — suppressed in chromeless embed; the host owns the chrome. */}
|
|
1232
|
+
{!chromeless && (
|
|
1146
1233
|
<header className="relative z-50 flex items-center justify-between px-4 py-2 bg-theme-base/90 backdrop-blur-sm border-b border-theme-border/50">
|
|
1147
1234
|
{/* Left: Logo + Cluster info */}
|
|
1148
1235
|
<div className="flex items-center gap-4 shrink-0">
|
|
1149
|
-
{
|
|
1236
|
+
{/* Standalone rail owns the brand; only the embedded/pill layout
|
|
1237
|
+
shows it in the header (host may override via brandSlot). */}
|
|
1238
|
+
{navCustomization.brandSlot ?? (showNavRail ? null : <Logo />)}
|
|
1150
1239
|
|
|
1151
1240
|
<div className="flex items-center gap-2">
|
|
1152
1241
|
{navCustomization.contextSlot ?? <ContextSwitcher ref={contextSwitcherRef} />}
|
|
@@ -1200,7 +1289,10 @@ function AppInner() {
|
|
|
1200
1289
|
</div>
|
|
1201
1290
|
</div>
|
|
1202
1291
|
|
|
1203
|
-
{/* Center: View tabs —
|
|
1292
|
+
{/* Center: View tabs — embedded/pill layout only. Standalone Radar
|
|
1293
|
+
navigates via the left rail (showNavRail), so the pill bar is
|
|
1294
|
+
suppressed there to avoid a duplicate primary nav. */}
|
|
1295
|
+
{!showNavRail && (
|
|
1204
1296
|
<div className="md:absolute md:left-1/2 md:-translate-x-1/2 flex items-center gap-0.5 bg-theme-elevated/50 rounded-full p-1 ml-2 md:ml-0">
|
|
1205
1297
|
{([
|
|
1206
1298
|
{ view: 'home' as const, icon: Home, label: 'Home' },
|
|
@@ -1217,7 +1309,7 @@ function AppInner() {
|
|
|
1217
1309
|
// Cost is intentionally hidden from the pill bar for now — the view still
|
|
1218
1310
|
// exists and is reachable via /cost, the Home dashboard card, and the
|
|
1219
1311
|
// command palette (⌘K). Remove this comment to restore it.
|
|
1220
|
-
{ view: '
|
|
1312
|
+
{ view: 'checks' as const, icon: ShieldCheck, label: 'Checks' },
|
|
1221
1313
|
] as const)
|
|
1222
1314
|
// In Cloud, Checks is a fleet-scoped feature owned by the host's
|
|
1223
1315
|
// left rail; the per-cluster view is just that fleet queue filtered
|
|
@@ -1227,7 +1319,7 @@ function AppInner() {
|
|
|
1227
1319
|
// via the Home "Cluster Audit" card (→ /audit, redirected to the
|
|
1228
1320
|
// scoped fleet Checks by the clusterChecksHref effect above), ⌘K,
|
|
1229
1321
|
// and bookmarks. Standalone OSS keeps the Audit tab.
|
|
1230
|
-
.filter(({ view }) => !(view === '
|
|
1322
|
+
.filter(({ view }) => !(view === 'checks' && clusterChecksHref))
|
|
1231
1323
|
.map(({ view, icon: Icon, label }) => (
|
|
1232
1324
|
<Tooltip key={view} content={label} delay={100} position="bottom">
|
|
1233
1325
|
<button
|
|
@@ -1254,6 +1346,30 @@ function AppInner() {
|
|
|
1254
1346
|
</Tooltip>
|
|
1255
1347
|
))}
|
|
1256
1348
|
</div>
|
|
1349
|
+
)}
|
|
1350
|
+
|
|
1351
|
+
{/* Center: omnibar — standalone search + command surface (the ⌘K entry).
|
|
1352
|
+
Fills the space the pill bar left; embedded keeps the pills + modal. */}
|
|
1353
|
+
{showNavRail && (
|
|
1354
|
+
<div className="hidden sm:flex flex-1 justify-center min-w-0 px-3">
|
|
1355
|
+
<Omnibar
|
|
1356
|
+
ref={omnibarRef}
|
|
1357
|
+
onNavigateView={(view) => setMainView(view)}
|
|
1358
|
+
onNavigateKind={(kind, group) => {
|
|
1359
|
+
const params = new URLSearchParams(searchParams)
|
|
1360
|
+
params.delete('kind')
|
|
1361
|
+
if (group) params.set('apiGroup', group); else params.delete('apiGroup')
|
|
1362
|
+
params.delete('resource')
|
|
1363
|
+
navigate({ pathname: `/resources/${kind}`, search: params.toString() })
|
|
1364
|
+
}}
|
|
1365
|
+
onSwitchContext={(name) => switchContext.mutate({ name }, { onSettled: () => setNamespaces([]) })}
|
|
1366
|
+
onSetNamespaces={(ns) => { setNamespaces(ns); setActiveNamespace.mutate({ namespaces: ns }) }}
|
|
1367
|
+
onToggleTheme={toggleTheme}
|
|
1368
|
+
onShowDiagnostics={() => setShowDiagnostics(true)}
|
|
1369
|
+
onOpenResource={(hit) => navigateToResourceList(searchHitToSelectedResource(hit))}
|
|
1370
|
+
/>
|
|
1371
|
+
</div>
|
|
1372
|
+
)}
|
|
1257
1373
|
|
|
1258
1374
|
{/* Right: Controls */}
|
|
1259
1375
|
<div className="flex items-center gap-3 shrink-0">
|
|
@@ -1264,7 +1380,9 @@ function AppInner() {
|
|
|
1264
1380
|
/>
|
|
1265
1381
|
|
|
1266
1382
|
|
|
1267
|
-
{/* Command palette trigger
|
|
1383
|
+
{/* Command palette trigger — embedded only; standalone has the
|
|
1384
|
+
top-center omnibar (which is the ⌘K surface). */}
|
|
1385
|
+
{!showNavRail && (
|
|
1268
1386
|
<button
|
|
1269
1387
|
onClick={() => setShowCommandPalette(true)}
|
|
1270
1388
|
className="hidden lg:flex items-center gap-2 h-7 px-2.5 rounded-md bg-theme-elevated hover:bg-theme-hover text-theme-text-secondary hover:text-theme-text-primary transition-colors"
|
|
@@ -1274,6 +1392,7 @@ function AppInner() {
|
|
|
1274
1392
|
{typeof navigator !== 'undefined' && navigator.platform.includes('Mac') ? '⌘' : 'Ctrl+'}K
|
|
1275
1393
|
</kbd>
|
|
1276
1394
|
</button>
|
|
1395
|
+
)}
|
|
1277
1396
|
|
|
1278
1397
|
{/* GitHub star — hidden in embedded mode (not OSS-distribution chrome). */}
|
|
1279
1398
|
{!navCustomization.embedded && (
|
|
@@ -1305,33 +1424,37 @@ function AppInner() {
|
|
|
1305
1424
|
</div>
|
|
1306
1425
|
)}
|
|
1307
1426
|
|
|
1308
|
-
{/*
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1427
|
+
{/* Help + Report-a-bug — standalone only (the left rail owns chrome;
|
|
1428
|
+
embedded hosts provide their own help/support). These replace the
|
|
1429
|
+
old floating bottom-right pair. Settings moved to the rail bottom. */}
|
|
1430
|
+
{showNavRail && (
|
|
1431
|
+
<>
|
|
1432
|
+
<button
|
|
1433
|
+
onClick={() => setShowHelp(true)}
|
|
1434
|
+
className="p-1.5 rounded-md bg-theme-elevated hover:bg-theme-hover text-theme-text-secondary hover:text-theme-text-primary transition-colors"
|
|
1435
|
+
title="Keyboard shortcuts (?)"
|
|
1436
|
+
>
|
|
1437
|
+
<HelpCircle className="w-4 h-4" />
|
|
1438
|
+
</button>
|
|
1439
|
+
<button
|
|
1440
|
+
onClick={() => setShowDiagnostics(true)}
|
|
1441
|
+
className="p-1.5 rounded-md bg-theme-elevated hover:bg-theme-hover text-theme-text-secondary hover:text-theme-text-primary transition-colors"
|
|
1442
|
+
title="Report a bug / Diagnostics"
|
|
1443
|
+
>
|
|
1444
|
+
<Bug className="w-4 h-4" />
|
|
1445
|
+
</button>
|
|
1446
|
+
</>
|
|
1324
1447
|
)}
|
|
1325
1448
|
|
|
1326
|
-
{/*
|
|
1327
|
-
|
|
1328
|
-
{!navCustomization.embedded && <UserMenu />}
|
|
1449
|
+
{/* Account moved to the rail bottom (standalone). Embedded never showed
|
|
1450
|
+
Radar's UserMenu — the host provides its own via rightExtras. */}
|
|
1329
1451
|
|
|
1330
1452
|
{/* Consumer-provided extras (e.g. Radar Hub's Install button +
|
|
1331
1453
|
avatar menu) appended to the right of the action bar. */}
|
|
1332
1454
|
{navCustomization.rightExtras}
|
|
1333
1455
|
</div>
|
|
1334
1456
|
</header>
|
|
1457
|
+
)}
|
|
1335
1458
|
|
|
1336
1459
|
{/* Auth barrier - show when auth is enabled but user is not authenticated */}
|
|
1337
1460
|
{authMe?.authEnabled && !authMe?.username && authMe.authMode === 'proxy' && (
|
|
@@ -1684,16 +1807,15 @@ function AppInner() {
|
|
|
1684
1807
|
fleet Checks queue (clusterChecksHref effect above) — render a brief
|
|
1685
1808
|
splash instead of the single-cluster view while the cross-document
|
|
1686
1809
|
nav lands. */}
|
|
1687
|
-
{mainView === '
|
|
1810
|
+
{mainView === 'checks' && clusterChecksHref && (
|
|
1688
1811
|
<div className="flex-1 flex flex-col items-center justify-center gap-3 bg-theme-base">
|
|
1689
1812
|
<img src={radarLoadingIcon} alt="" aria-hidden className="w-11 h-11" />
|
|
1690
1813
|
<p className="text-sm text-theme-text-secondary">Opening Checks…</p>
|
|
1691
1814
|
</div>
|
|
1692
1815
|
)}
|
|
1693
|
-
{mainView === '
|
|
1816
|
+
{mainView === 'checks' && !clusterChecksHref && (
|
|
1694
1817
|
<AuditView
|
|
1695
1818
|
namespaces={namespaces}
|
|
1696
|
-
onBack={() => setMainView('home')}
|
|
1697
1819
|
onNavigateToResource={navigateToResourceList}
|
|
1698
1820
|
/>
|
|
1699
1821
|
)}
|
|
@@ -1705,7 +1827,6 @@ function AppInner() {
|
|
|
1705
1827
|
{mainView === 'issues' && (
|
|
1706
1828
|
<IssuesPane
|
|
1707
1829
|
namespaces={namespaces}
|
|
1708
|
-
onBack={() => setMainView('home')}
|
|
1709
1830
|
onNavigateToResource={navigateFromIssue}
|
|
1710
1831
|
/>
|
|
1711
1832
|
)}
|
|
@@ -1783,8 +1904,12 @@ function AppInner() {
|
|
|
1783
1904
|
{/* Spacer for dock */}
|
|
1784
1905
|
<DockSpacer />
|
|
1785
1906
|
|
|
1786
|
-
{/* Floating action buttons —
|
|
1787
|
-
|
|
1907
|
+
{/* Floating action buttons — embedded only, and not in chromeless (the
|
|
1908
|
+
host owns help/diagnostics chrome). Standalone moved help + bug to
|
|
1909
|
+
visible top-bar icons (the rail owns chrome). */}
|
|
1910
|
+
{!showNavRail && !chromeless && (
|
|
1911
|
+
<FloatingButtons showHelp={showHelp} showCommandPalette={showCommandPalette} showDiagnostics={showDiagnostics} onHelp={() => setShowHelp(true)} onBugReport={() => setShowDiagnostics(true)} />
|
|
1912
|
+
)}
|
|
1788
1913
|
|
|
1789
1914
|
{/* Keyboard shortcut help overlay */}
|
|
1790
1915
|
{helpOverlay.shouldRender && <ShortcutHelpOverlay isOpen={helpOverlay.isOpen} onClose={() => setShowHelp(false)} currentView={mainView} />}
|
|
@@ -1841,6 +1966,7 @@ function AppInner() {
|
|
|
1841
1966
|
|
|
1842
1967
|
{/* Debug overlay - only in dev mode */}
|
|
1843
1968
|
{import.meta.env.DEV && <DebugOverlay />}
|
|
1969
|
+
</div>
|
|
1844
1970
|
</div>
|
|
1845
1971
|
</PortForwardProvider>
|
|
1846
1972
|
)
|
package/src/RadarApp.tsx
CHANGED
|
@@ -70,6 +70,13 @@ export interface RadarAppProps {
|
|
|
70
70
|
* See ./context/NavCustomization for the slot shape.
|
|
71
71
|
*/
|
|
72
72
|
navSlots?: NavCustomization;
|
|
73
|
+
/**
|
|
74
|
+
* Initial route for `router: 'memory'` (ignored for 'browser'). Lets a host
|
|
75
|
+
* deep-link a specific view (e.g. '/topology') without owning the URL bar —
|
|
76
|
+
* used with `navSlots.chrome: 'none'` to render a single per-cluster view
|
|
77
|
+
* chromeless under the host's own chrome (Radar Hub's per-cluster destinations).
|
|
78
|
+
*/
|
|
79
|
+
initialPath?: string;
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
// Default QueryClient with the same shape Radar's standalone binary uses.
|
|
@@ -109,6 +116,7 @@ export function RadarApp({
|
|
|
109
116
|
router = 'browser',
|
|
110
117
|
queryClient,
|
|
111
118
|
navSlots,
|
|
119
|
+
initialPath,
|
|
112
120
|
}: RadarAppProps): React.ReactElement {
|
|
113
121
|
// Apply runtime config during render so module-level singletons are set
|
|
114
122
|
// before children construct URLs. getApiBase() / getAuthHeaders() /
|
|
@@ -136,7 +144,7 @@ export function RadarApp({
|
|
|
136
144
|
);
|
|
137
145
|
|
|
138
146
|
if (router === 'memory') {
|
|
139
|
-
return <MemoryRouter initialEntries={['/']}>{inner}</MemoryRouter>;
|
|
147
|
+
return <MemoryRouter initialEntries={[initialPath || '/']}>{inner}</MemoryRouter>;
|
|
140
148
|
}
|
|
141
149
|
|
|
142
150
|
return <BrowserRouter basename={basename || undefined}>{inner}</BrowserRouter>;
|