@skyhook-io/radar-app 1.6.2 → 1.7.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 +1 -1
- package/src/App.tsx +73 -36
- package/src/api/client.ts +10 -6
- package/src/components/helm/HelmView.tsx +4 -4
- package/src/components/home/HomeView.tsx +9 -2
- package/src/context/NavCustomization.tsx +34 -9
- package/src/index.ts +1 -1
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -29,6 +29,7 @@ import { DURATION_DOCK } from '@skyhook-io/k8s-ui/utils/animation'
|
|
|
29
29
|
import { ContextSwitcher } from './components/ContextSwitcher'
|
|
30
30
|
import { NamespaceSwitcher, type NamespaceSwitcherHandle } from './components/NamespaceSwitcher'
|
|
31
31
|
import { useNavCustomization } from './context/NavCustomization'
|
|
32
|
+
import type { FleetTakeoverTarget } from './context/NavCustomization'
|
|
32
33
|
import { PrimaryNavRail } from './components/nav/PrimaryNavRail'
|
|
33
34
|
import { useNavRailPinned } from './hooks/useNavRailPinned'
|
|
34
35
|
import { useMediaQuery } from './hooks/useMediaQuery'
|
|
@@ -276,21 +277,46 @@ function AppInner() {
|
|
|
276
277
|
navigate({ pathname: path, search: newParams.toString() })
|
|
277
278
|
}, [navigate, searchParams])
|
|
278
279
|
|
|
279
|
-
// Cloud (embedded)
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
//
|
|
280
|
+
// Cloud (embedded) takes over the "fleet-shaped" per-cluster views with its
|
|
281
|
+
// own fleet pages scoped to this cluster — owned by the host's left rail — so
|
|
282
|
+
// Radar drops the matching pills (see the nav below) and any route into one
|
|
283
|
+
// of these views redirects to the host. Entry points that still land on the
|
|
284
|
+
// view — Home cards, ⌘K, WorkloadView's "view all" findings, bookmarks/deep
|
|
285
|
+
// links — all funnel through here. The view name doubles as the takeover
|
|
286
|
+
// target ('issues' | 'gitops' | 'checks'; 'audit' normalizes to 'checks' via
|
|
287
|
+
// getViewFromPath). `replace` (not assign) keeps the transient /<view> URL
|
|
288
|
+
// out of history so Back doesn't bounce off the redirect. Standalone OSS (no
|
|
289
|
+
// fleetTakeoverHref) is unaffected and renders the in-app view as before.
|
|
290
|
+
// Resolve every takeover target's host URL ONCE per render so the redirect
|
|
291
|
+
// effect, nav-pill filtering, inline-view gating, and the cert click handler
|
|
292
|
+
// all close over the SAME value — host callbacks aren't guaranteed idempotent
|
|
293
|
+
// (scope / flags / signed URLs can shift between calls). undefined = not taken
|
|
294
|
+
// over → Radar renders the view itself. `clusterChecksHref` is the deprecated
|
|
295
|
+
// pre-1.7 hook, folded into the 'checks' target for back-compat.
|
|
296
|
+
const fleetTakeoverHref = navCustomization.fleetTakeoverHref
|
|
288
297
|
const clusterChecksHref = navCustomization.clusterChecksHref
|
|
298
|
+
const takeover: Record<FleetTakeoverTarget, string | undefined> = {
|
|
299
|
+
issues: fleetTakeoverHref?.('issues'),
|
|
300
|
+
gitops: fleetTakeoverHref?.('gitops'),
|
|
301
|
+
checks: fleetTakeoverHref?.('checks') ?? clusterChecksHref?.(),
|
|
302
|
+
certs: fleetTakeoverHref?.('certs'),
|
|
303
|
+
}
|
|
304
|
+
// Has the host claimed this view? View-shaped targets only ('certs' has no
|
|
305
|
+
// Radar view — only its Home card consults `takeover`). Used to drop the nav
|
|
306
|
+
// pill and gate the inline view render in favor of the "Opening…" splash.
|
|
307
|
+
const isViewTakenOver = (view: ExtendedMainView): boolean =>
|
|
308
|
+
(view === 'issues' || view === 'gitops' || view === 'checks') && !!takeover[view]
|
|
309
|
+
// The host's URL for the CURRENT view, if taken over. Drives the redirect
|
|
310
|
+
// effect and the "Opening…" splash.
|
|
311
|
+
const viewTakeoverHref =
|
|
312
|
+
mainView === 'issues' || mainView === 'gitops' || mainView === 'checks'
|
|
313
|
+
? takeover[mainView]
|
|
314
|
+
: undefined
|
|
289
315
|
useEffect(() => {
|
|
290
|
-
if (
|
|
291
|
-
window.location.replace(
|
|
316
|
+
if (viewTakeoverHref) {
|
|
317
|
+
window.location.replace(viewTakeoverHref)
|
|
292
318
|
}
|
|
293
|
-
}, [
|
|
319
|
+
}, [viewTakeoverHref])
|
|
294
320
|
|
|
295
321
|
const [namespaces, setNamespaces] = useState<string[]>(getInitialState().namespaces)
|
|
296
322
|
// For large clusters: force SSE to reconnect with namespace filter
|
|
@@ -1318,15 +1344,14 @@ function AppInner() {
|
|
|
1318
1344
|
// command palette (⌘K). Remove this comment to restore it.
|
|
1319
1345
|
{ view: 'checks' as const, icon: ShieldCheck, label: 'Checks' },
|
|
1320
1346
|
] as const)
|
|
1321
|
-
// In Cloud, Checks
|
|
1322
|
-
// left rail; the per-cluster view is just that fleet
|
|
1323
|
-
// to this cluster, so duplicating it as a peer pill here
|
|
1324
|
-
// second
|
|
1325
|
-
//
|
|
1326
|
-
// via the Home
|
|
1327
|
-
//
|
|
1328
|
-
|
|
1329
|
-
.filter(({ view }) => !(view === 'checks' && clusterChecksHref))
|
|
1347
|
+
// In Cloud, fleet-shaped views (Checks, Issues, GitOps) are owned by
|
|
1348
|
+
// the host's left rail; the per-cluster view is just that fleet page
|
|
1349
|
+
// filtered to this cluster, so duplicating it as a peer pill here
|
|
1350
|
+
// would be a second copy that teleports out of the cluster shell.
|
|
1351
|
+
// Drop any pill the host took over — cluster-scoped access stays
|
|
1352
|
+
// available via the Home cards (redirected by the takeover effect
|
|
1353
|
+
// above), ⌘K, and bookmarks. Standalone OSS keeps every pill.
|
|
1354
|
+
.filter(({ view }) => !isViewTakenOver(view))
|
|
1330
1355
|
.map(({ view, icon: Icon, label }) => (
|
|
1331
1356
|
<Tooltip key={view} content={label} delay={100} position="bottom">
|
|
1332
1357
|
<button
|
|
@@ -1382,8 +1407,6 @@ function AppInner() {
|
|
|
1382
1407
|
<div className="flex items-center gap-3 shrink-0">
|
|
1383
1408
|
<NamespaceSwitcher
|
|
1384
1409
|
ref={namespaceSwitcherRef}
|
|
1385
|
-
disabled={mainView === 'helm'}
|
|
1386
|
-
disabledTooltip="Helm view always shows all namespaces"
|
|
1387
1410
|
/>
|
|
1388
1411
|
|
|
1389
1412
|
|
|
@@ -1611,6 +1634,15 @@ function AppInner() {
|
|
|
1611
1634
|
navigate({ pathname: `/resources/${kind}`, search: newParams.toString() })
|
|
1612
1635
|
}}
|
|
1613
1636
|
onNavigateToResource={navigateFromIssue}
|
|
1637
|
+
// Certs has no Radar view, so it can't ride the view-redirect effect
|
|
1638
|
+
// above — wire the Certificate Health card straight to the host's
|
|
1639
|
+
// fleet Certs page (scoped to this cluster) when claimed. `assign`
|
|
1640
|
+
// (not replace): the user is navigating forward from a card, so this
|
|
1641
|
+
// belongs in history. Omitted → the card falls back to Radar's own
|
|
1642
|
+
// TLS-secrets resource list.
|
|
1643
|
+
onNavigateToCerts={
|
|
1644
|
+
takeover.certs ? () => window.location.assign(takeover.certs!) : undefined
|
|
1645
|
+
}
|
|
1614
1646
|
/>
|
|
1615
1647
|
)}
|
|
1616
1648
|
|
|
@@ -1746,10 +1778,9 @@ function AppInner() {
|
|
|
1746
1778
|
/>
|
|
1747
1779
|
)}
|
|
1748
1780
|
|
|
1749
|
-
{/* Helm view - always show all namespaces since releases span multiple ns */}
|
|
1750
1781
|
{mainView === 'helm' && (
|
|
1751
1782
|
<HelmView
|
|
1752
|
-
|
|
1783
|
+
namespaces={namespaces}
|
|
1753
1784
|
selectedRelease={selectedHelmRelease}
|
|
1754
1785
|
onReleaseClick={(ns, name, storageNamespace) => {
|
|
1755
1786
|
setSelectedHelmRelease({ namespace: ns, name, storageNamespace })
|
|
@@ -1765,8 +1796,9 @@ function AppInner() {
|
|
|
1765
1796
|
/>
|
|
1766
1797
|
)}
|
|
1767
1798
|
|
|
1768
|
-
{/* GitOps view
|
|
1769
|
-
|
|
1799
|
+
{/* GitOps view (inline only when the host hasn't taken it over — see
|
|
1800
|
+
the takeover splash below). */}
|
|
1801
|
+
{mainView === 'gitops' && !isViewTakenOver('gitops') && (
|
|
1770
1802
|
<GitOpsView
|
|
1771
1803
|
namespaces={namespaces}
|
|
1772
1804
|
onOpenResource={(resource) => {
|
|
@@ -1796,17 +1828,21 @@ function AppInner() {
|
|
|
1796
1828
|
<CostView onBack={() => setMainView('home')} />
|
|
1797
1829
|
)}
|
|
1798
1830
|
|
|
1799
|
-
{/*
|
|
1800
|
-
|
|
1801
|
-
splash instead of the
|
|
1802
|
-
nav lands.
|
|
1803
|
-
|
|
1831
|
+
{/* Takeover splash. When the host claims the current view via
|
|
1832
|
+
fleetTakeoverHref, the redirect effect above is mid-flight — render a
|
|
1833
|
+
brief splash instead of the inline view (which would flash + fire its
|
|
1834
|
+
own fetches) while the cross-document nav lands. Covers checks /
|
|
1835
|
+
issues / gitops with one block since only one view is active. */}
|
|
1836
|
+
{viewTakeoverHref && (
|
|
1804
1837
|
<div className="flex-1 flex flex-col items-center justify-center gap-3 bg-theme-base">
|
|
1805
1838
|
<img src={radarLoadingIcon} alt="" aria-hidden className="w-11 h-11" />
|
|
1806
|
-
<p className="text-sm text-theme-text-secondary">Opening
|
|
1839
|
+
<p className="text-sm text-theme-text-secondary">Opening…</p>
|
|
1807
1840
|
</div>
|
|
1808
1841
|
)}
|
|
1809
|
-
|
|
1842
|
+
|
|
1843
|
+
{/* Best practices detail view (inline only when the host hasn't taken
|
|
1844
|
+
Checks over — standalone OSS, or Cloud without a checks takeover). */}
|
|
1845
|
+
{mainView === 'checks' && !isViewTakenOver('checks') && (
|
|
1810
1846
|
<AuditView
|
|
1811
1847
|
namespaces={namespaces}
|
|
1812
1848
|
onNavigateToResource={navigateToResourceList}
|
|
@@ -1816,8 +1852,9 @@ function AppInner() {
|
|
|
1816
1852
|
{/* Issues — per-cluster live triage queue (hidden route: not yet in the
|
|
1817
1853
|
nav `views` list; reachable at /issues). Same shared <IssuesView> the
|
|
1818
1854
|
Hub fleet uses; a GitOps reconciler subject routes to its detail page,
|
|
1819
|
-
other resources open the standard resource view.
|
|
1820
|
-
|
|
1855
|
+
other resources open the standard resource view. Inline only when the
|
|
1856
|
+
host hasn't taken it over. */}
|
|
1857
|
+
{mainView === 'issues' && !isViewTakenOver('issues') && (
|
|
1821
1858
|
<IssuesPane
|
|
1822
1859
|
namespaces={namespaces}
|
|
1823
1860
|
onNavigateToResource={navigateFromIssue}
|
package/src/api/client.ts
CHANGED
|
@@ -2311,11 +2311,15 @@ export function useDrainNode() {
|
|
|
2311
2311
|
// Helm API hooks
|
|
2312
2312
|
// ============================================================================
|
|
2313
2313
|
|
|
2314
|
+
function helmNamespaceParams(namespaces: string[] = []) {
|
|
2315
|
+
return namespaces.length > 0 ? `?namespaces=${namespaces.join(',')}` : ''
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2314
2318
|
// List all Helm releases
|
|
2315
|
-
export function useHelmReleases(
|
|
2316
|
-
const params =
|
|
2319
|
+
export function useHelmReleases(namespaces: string[] = []) {
|
|
2320
|
+
const params = helmNamespaceParams(namespaces)
|
|
2317
2321
|
return useQuery<HelmRelease[]>({
|
|
2318
|
-
queryKey: ['helm-releases',
|
|
2322
|
+
queryKey: ['helm-releases', namespaces],
|
|
2319
2323
|
queryFn: () => fetchJSON(`/helm/releases${params}`),
|
|
2320
2324
|
staleTime: 30000, // 30 seconds
|
|
2321
2325
|
})
|
|
@@ -2393,10 +2397,10 @@ export function useHelmUpgradeInfo(namespace: string, name: string, enabled = tr
|
|
|
2393
2397
|
}
|
|
2394
2398
|
|
|
2395
2399
|
// Batch check for upgrade availability (for list view)
|
|
2396
|
-
export function useHelmBatchUpgradeInfo(
|
|
2397
|
-
const params =
|
|
2400
|
+
export function useHelmBatchUpgradeInfo(namespaces: string[] = [], enabled = true) {
|
|
2401
|
+
const params = helmNamespaceParams(namespaces)
|
|
2398
2402
|
return useQuery<BatchUpgradeInfo>({
|
|
2399
|
-
queryKey: ['helm-batch-upgrade-info',
|
|
2403
|
+
queryKey: ['helm-batch-upgrade-info', namespaces],
|
|
2400
2404
|
queryFn: () => fetchJSON(`/helm/upgrade-check${params}`),
|
|
2401
2405
|
enabled,
|
|
2402
2406
|
staleTime: 30000, // 30 seconds - keep in sync with release list
|
|
@@ -15,23 +15,23 @@ import { InstallWizard } from './InstallWizard'
|
|
|
15
15
|
type ViewTab = 'releases' | 'charts'
|
|
16
16
|
|
|
17
17
|
interface HelmViewProps {
|
|
18
|
-
|
|
18
|
+
namespaces: string[]
|
|
19
19
|
selectedRelease?: SelectedHelmRelease | null
|
|
20
20
|
onReleaseClick?: (namespace: string, name: string, storageNamespace?: string) => void
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export function HelmView({
|
|
23
|
+
export function HelmView({ namespaces, selectedRelease, onReleaseClick }: HelmViewProps) {
|
|
24
24
|
const [activeTab, setActiveTab] = useState<ViewTab>('releases')
|
|
25
25
|
const [searchTerm, setSearchTerm] = useState('')
|
|
26
26
|
const [selectedChart, setSelectedChart] = useState<{ repo: string; chart: string; version: string; source: ChartSource } | null>(null)
|
|
27
27
|
|
|
28
|
-
const { data: releases, isLoading, error: releasesError, refetch: refetchReleases } = useHelmReleases(
|
|
28
|
+
const { data: releases, isLoading, error: releasesError, refetch: refetchReleases } = useHelmReleases(namespaces)
|
|
29
29
|
const isForbidden = isForbiddenError(releasesError)
|
|
30
30
|
const releasesErrorMessage = releasesError instanceof Error ? releasesError.message : 'Failed to load Helm releases'
|
|
31
31
|
|
|
32
32
|
// Lazy load upgrade info after releases are loaded
|
|
33
33
|
const { data: upgradeInfo, isLoading: upgradeLoading, error: upgradeError, refetch: refetchUpgradeInfo } = useHelmBatchUpgradeInfo(
|
|
34
|
-
|
|
34
|
+
namespaces,
|
|
35
35
|
Boolean(releases && releases.length > 0)
|
|
36
36
|
)
|
|
37
37
|
const upgradeErrorMessage = upgradeError instanceof Error ? upgradeError.message : 'Upgrade checks failed'
|
|
@@ -29,9 +29,16 @@ interface HomeViewProps {
|
|
|
29
29
|
onNavigateToView: (view: ExtendedMainView, params?: Record<string, string>) => void
|
|
30
30
|
onNavigateToResourceKind: (kind: string, group?: string, filters?: Record<string, string[]>) => void
|
|
31
31
|
onNavigateToResource: (resource: SelectedResource) => void
|
|
32
|
+
/**
|
|
33
|
+
* Optional override for the Certificate Health card's click. When an embedded
|
|
34
|
+
* host (Radar Cloud) takes Certs over with its own fleet page, it passes this
|
|
35
|
+
* to route there instead of Radar's TLS-secrets resource list. Omitted in
|
|
36
|
+
* standalone OSS → the card drills into secrets as before.
|
|
37
|
+
*/
|
|
38
|
+
onNavigateToCerts?: () => void
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
export function HomeView({ namespaces, topology, onNavigateToView, onNavigateToResourceKind, onNavigateToResource }: HomeViewProps) {
|
|
41
|
+
export function HomeView({ namespaces, topology, onNavigateToView, onNavigateToResourceKind, onNavigateToResource, onNavigateToCerts }: HomeViewProps) {
|
|
35
42
|
const { data, isLoading, error } = useDashboard(namespaces)
|
|
36
43
|
const { data: issuesData, isLoading: issuesLoading, isFetching: issuesFetching, error: issuesError } = useIssues(namespaces)
|
|
37
44
|
const issues = issuesData?.issues ?? []
|
|
@@ -165,7 +172,7 @@ export function HomeView({ namespaces, topology, onNavigateToView, onNavigateToR
|
|
|
165
172
|
<BandItem>
|
|
166
173
|
<CertificateHealthCard
|
|
167
174
|
data={data.certificateHealth}
|
|
168
|
-
onNavigate={() => onNavigateToResourceKind('secrets', undefined, { type: ['TLS'] })}
|
|
175
|
+
onNavigate={onNavigateToCerts ?? (() => onNavigateToResourceKind('secrets', undefined, { type: ['TLS'] }))}
|
|
169
176
|
/>
|
|
170
177
|
</BandItem>
|
|
171
178
|
)}
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
import { createContext, useContext } from 'react';
|
|
14
14
|
import type { ReactNode } from 'react';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Per-cluster destinations an embedded host can take over with its own
|
|
18
|
+
* fleet-scoped pages. See `fleetTakeoverHref`. 'issues' | 'gitops' | 'checks'
|
|
19
|
+
* are also Radar view names (so route entry redirects too); 'certs' is
|
|
20
|
+
* card-only (Radar has no certs view).
|
|
21
|
+
*/
|
|
22
|
+
export type FleetTakeoverTarget = 'issues' | 'gitops' | 'checks' | 'certs';
|
|
23
|
+
|
|
16
24
|
interface NavCustomizationBase {
|
|
17
25
|
/** Replaces Radar's Skyhook/radar logo + wordmark. */
|
|
18
26
|
brandSlot?: ReactNode;
|
|
@@ -32,15 +40,32 @@ interface NavCustomizationBase {
|
|
|
32
40
|
group?: string;
|
|
33
41
|
}) => string;
|
|
34
42
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
43
|
+
* Lets an embedded host (e.g. Radar Cloud) take over selected per-cluster
|
|
44
|
+
* destinations with its OWN fleet pages scoped to this cluster, instead of
|
|
45
|
+
* Radar rendering them inline. Given a semantic target the host returns the
|
|
46
|
+
* URL to navigate to, or `undefined`/omits the hook to let Radar render its
|
|
47
|
+
* own view as usual (standalone OSS does the latter for everything).
|
|
48
|
+
*
|
|
49
|
+
* This is how the Home dashboard's "fleet-shaped" cards reach the host's
|
|
50
|
+
* canonical surfaces rather than a second, diverging per-cluster copy:
|
|
51
|
+
* - 'issues' → the Active Issues panel + cluster-health issues count
|
|
52
|
+
* - 'gitops' → the GitOps controllers card
|
|
53
|
+
* - 'checks' → the Cluster Audit card (and any route to /audit; legacy
|
|
54
|
+
* `clusterChecksHref` folded in here)
|
|
55
|
+
* - 'certs' → the Certificate Health card
|
|
56
|
+
*
|
|
57
|
+
* View-shaped targets (issues / gitops / checks) are also honored for any
|
|
58
|
+
* entry into that view — ⌘K, bookmarks, deep links — via a redirect effect
|
|
59
|
+
* in App.tsx, using window.location.replace so the transient /<view> URL
|
|
60
|
+
* stays out of history. 'certs' has no Radar view, so only the card consults
|
|
61
|
+
* it (window.location.assign — a real forward navigation the user initiated).
|
|
62
|
+
*/
|
|
63
|
+
fleetTakeoverHref?: (target: FleetTakeoverTarget) => string | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated Superseded by `fleetTakeoverHref('checks')`. Kept so consumers
|
|
66
|
+
* still on the pre-1.7 hook keep working (App.tsx folds it into the 'checks'
|
|
67
|
+
* target) — this makes adding `fleetTakeoverHref` an additive, non-breaking
|
|
68
|
+
* change. Remove in a major release once all consumers have migrated.
|
|
44
69
|
*/
|
|
45
70
|
clusterChecksHref?: () => string;
|
|
46
71
|
/**
|
package/src/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ export {
|
|
|
14
14
|
getAuthHeaders,
|
|
15
15
|
getCredentialsMode,
|
|
16
16
|
} from './api/config';
|
|
17
|
-
export type { NavCustomization } from './context/NavCustomization';
|
|
17
|
+
export type { NavCustomization, FleetTakeoverTarget } from './context/NavCustomization';
|
|
18
18
|
export { ShortcutHelpOverlay } from './components/ui/ShortcutHelpOverlay';
|
|
19
19
|
|
|
20
20
|
// Shared cluster-switcher primitive — re-exported from @skyhook-io/k8s-ui so
|