@skyhook-io/radar-app 1.7.0 → 1.8.1
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 +9 -3
- package/src/App.tsx +150 -44
- package/src/api/client.ts +3 -3
- package/src/components/ConnectionErrorView.tsx +5 -2
- package/src/components/DebugOverlay.tsx +5 -3
- package/src/components/UserMenu.tsx +5 -2
- package/src/components/applications/ApplicationsView.tsx +1 -1
- package/src/components/audit/AuditSettingsDialog.tsx +14 -10
- package/src/components/audit/AuditView.tsx +3 -1
- package/src/components/compare/CompareViewRoute.tsx +13 -5
- package/src/components/compare/useCompareLauncher.tsx +6 -3
- package/src/components/cost/CostTrendChart.tsx +3 -3
- package/src/components/cost/CostView.tsx +14 -5
- package/src/components/helm/ChartBrowser.tsx +7 -5
- package/src/components/helm/HelmReleaseDrawer.tsx +25 -13
- package/src/components/helm/HelmView.tsx +3 -2
- package/src/components/helm/InstallWizard.tsx +4 -3
- 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/ActivitySummary.tsx +4 -1
- 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/issues/IssuesPane.tsx +2 -2
- package/src/components/nav/PrimaryNavRail.tsx +5 -2
- package/src/components/portforward/PortForwardButton.tsx +14 -8
- package/src/components/portforward/PortForwardManager.tsx +70 -8
- package/src/components/resource/PrometheusChartsGrid.tsx +10 -3
- 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/resources/ResourcesView.tsx +2 -0
- package/src/components/settings/MyPermissionsDialog.tsx +64 -4
- package/src/components/settings/SettingsDialog.tsx +371 -141
- package/src/components/shared/LargeClusterNamespacePicker.tsx +1 -1
- package/src/components/traffic/TrafficFilterSidebar.tsx +8 -43
- package/src/components/traffic/TrafficFlowList.tsx +13 -4
- package/src/components/traffic/TrafficGraph.tsx +37 -23
- package/src/components/traffic/TrafficView.tsx +7 -5
- package/src/components/ui/DiagnosticsOverlay.tsx +1 -1
- package/src/components/ui/Omnibar.tsx +0 -1
- package/src/components/workload/WorkloadView.tsx +32 -27
- package/src/context/NavCustomization.tsx +19 -5
- package/src/main.tsx +1 -1
|
@@ -23,6 +23,7 @@ import { getSkeletonYaml } from '../../utils/skeleton-yaml'
|
|
|
23
23
|
interface ResourceCountsResponse {
|
|
24
24
|
counts: Record<string, number>
|
|
25
25
|
forbidden?: string[]
|
|
26
|
+
reasons?: Record<string, string>
|
|
26
27
|
unavailable?: string[]
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -284,6 +285,7 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
284
285
|
// Lightweight counts for sidebar (replaces 233 parallel queries)
|
|
285
286
|
resourceCounts={countsData?.counts}
|
|
286
287
|
resourceForbidden={countsData?.forbidden}
|
|
288
|
+
resourceReasons={countsData?.reasons}
|
|
287
289
|
resourceUnavailable={countsData?.unavailable}
|
|
288
290
|
selectedKindQuery={selectedKindQueryResult}
|
|
289
291
|
largeListGuard={largeListGuard}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from 'react'
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
|
-
import { Shield, X, Loader2 } from 'lucide-react'
|
|
3
|
+
import { Shield, X, Loader2, Lock, ExternalLink } from 'lucide-react'
|
|
4
4
|
import { clsx } from 'clsx'
|
|
5
|
+
import { useQuery } from '@tanstack/react-query'
|
|
5
6
|
import {
|
|
6
7
|
rbacVerbBadgeClass,
|
|
7
8
|
rbacResourceBadgeClass,
|
|
8
9
|
rbacApiGroupBadgeClass,
|
|
9
10
|
rbacResourceNameBadgeClass,
|
|
10
11
|
rbacNonResourceUrlBadgeClass,
|
|
12
|
+
type RBACWhoamiResponse,
|
|
11
13
|
} from '@skyhook-io/k8s-ui'
|
|
12
14
|
import { useAnimatedUnmount } from '../../hooks/useAnimatedUnmount'
|
|
13
15
|
import { TRANSITION_BACKDROP, TRANSITION_PANEL } from '../../utils/animation'
|
|
14
|
-
import { useNamespaces, useAuthMe } from '../../api/client'
|
|
16
|
+
import { useNamespaces, useAuthMe, fetchJSON } from '../../api/client'
|
|
15
17
|
import { useRBACWhoami } from '../../api/rbac'
|
|
16
18
|
|
|
17
19
|
interface MyPermissionsDialogProps {
|
|
@@ -71,7 +73,7 @@ export function MyPermissionsDialog({ open, onClose }: MyPermissionsDialogProps)
|
|
|
71
73
|
<div className="flex items-center justify-between p-4 border-b border-theme-border shrink-0">
|
|
72
74
|
<div className="flex items-center gap-2">
|
|
73
75
|
<Shield className="w-5 h-5 text-theme-text-secondary" />
|
|
74
|
-
<h2 className="text-lg font-semibold text-theme-text-primary">
|
|
76
|
+
<h2 className="text-lg font-semibold text-theme-text-primary">Your access on this cluster</h2>
|
|
75
77
|
</div>
|
|
76
78
|
<button
|
|
77
79
|
onClick={onClose}
|
|
@@ -134,6 +136,8 @@ export function MyPermissionsDialog({ open, onClose }: MyPermissionsDialogProps)
|
|
|
134
136
|
) : whoami ? (
|
|
135
137
|
<PermissionsTable whoami={whoami} />
|
|
136
138
|
) : null}
|
|
139
|
+
|
|
140
|
+
<RestrictedResources enabled={open} />
|
|
137
141
|
</div>
|
|
138
142
|
</div>
|
|
139
143
|
</div>,
|
|
@@ -141,7 +145,7 @@ export function MyPermissionsDialog({ open, onClose }: MyPermissionsDialogProps)
|
|
|
141
145
|
)
|
|
142
146
|
}
|
|
143
147
|
|
|
144
|
-
function PermissionsTable({ whoami }: { whoami:
|
|
148
|
+
function PermissionsTable({ whoami }: { whoami: RBACWhoamiResponse }) {
|
|
145
149
|
const resourceRules = whoami.resourceRules ?? []
|
|
146
150
|
const nonResourceRules = whoami.nonResourceRules ?? []
|
|
147
151
|
|
|
@@ -229,3 +233,59 @@ function ResourceRuleRow({ rule }: { rule: { verbs?: string[]; apiGroups?: strin
|
|
|
229
233
|
</div>
|
|
230
234
|
)
|
|
231
235
|
}
|
|
236
|
+
|
|
237
|
+
// displayKind strips the API group from a resource-counts key ("group/Kind" →
|
|
238
|
+
// "Kind"; core kinds have no prefix).
|
|
239
|
+
function displayKind(countKey: string): string {
|
|
240
|
+
const i = countKey.indexOf('/')
|
|
241
|
+
return i === -1 ? countKey : countKey.slice(i + 1)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// RestrictedResources surfaces the kinds Radar isn't showing the user (the
|
|
245
|
+
// resource-counts `forbidden` set) — the "what's hidden from me" half of access,
|
|
246
|
+
// alongside the SelfSubjectRulesReview rules above. That set mixes RBAC denials
|
|
247
|
+
// with not-installed/not-watched kinds, so the copy says "usually RBAC" rather
|
|
248
|
+
// than asserting a cause, and links to the docs that carry the unblock RBAC.
|
|
249
|
+
function RestrictedResources({ enabled }: { enabled: boolean }) {
|
|
250
|
+
const { data } = useQuery<{ forbidden?: string[] }>({
|
|
251
|
+
queryKey: ['resource-counts', 'your-access'],
|
|
252
|
+
queryFn: () => fetchJSON('/resource-counts'),
|
|
253
|
+
enabled,
|
|
254
|
+
staleTime: 10000,
|
|
255
|
+
})
|
|
256
|
+
const forbidden = data?.forbidden ?? []
|
|
257
|
+
if (forbidden.length === 0) return null
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div>
|
|
261
|
+
<div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-2 flex items-center gap-1.5">
|
|
262
|
+
<Lock className="w-3.5 h-3.5 text-amber-400" />
|
|
263
|
+
Restricted or unavailable ({forbidden.length})
|
|
264
|
+
</div>
|
|
265
|
+
<p className="text-xs text-theme-text-tertiary mb-2">
|
|
266
|
+
Resource types Radar isn't showing you — usually because your RBAC doesn't allow listing
|
|
267
|
+
them, sometimes because the type isn't installed or watched on this cluster. Either way,
|
|
268
|
+
not an empty cluster.
|
|
269
|
+
</p>
|
|
270
|
+
<div className="flex flex-wrap gap-1.5">
|
|
271
|
+
{forbidden.map((k) => (
|
|
272
|
+
<span
|
|
273
|
+
key={k}
|
|
274
|
+
className="inline-flex items-center px-2 py-0.5 text-xs rounded border border-theme-border bg-theme-elevated text-theme-text-secondary"
|
|
275
|
+
>
|
|
276
|
+
{displayKind(k)}
|
|
277
|
+
</span>
|
|
278
|
+
))}
|
|
279
|
+
</div>
|
|
280
|
+
<a
|
|
281
|
+
href="https://radarhq.io/docs/cloud/rbac"
|
|
282
|
+
target="_blank"
|
|
283
|
+
rel="noreferrer"
|
|
284
|
+
className="inline-flex items-center gap-1 text-xs text-accent-text hover:underline mt-2"
|
|
285
|
+
>
|
|
286
|
+
How to get access
|
|
287
|
+
<ExternalLink className="w-3 h-3" />
|
|
288
|
+
</a>
|
|
289
|
+
</div>
|
|
290
|
+
)
|
|
291
|
+
}
|