@skyhook-io/radar-app 0.2.2 → 0.3.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/README.md +7 -1
- package/package.json +33 -25
- package/src/App.tsx +1449 -382
- package/src/RadarApp.tsx +132 -19
- package/src/api/apiResources.ts +1 -1
- package/src/api/client.argoResourceSync.test.ts +69 -0
- package/src/api/client.delta.test.ts +89 -0
- package/src/api/client.deltaSync.test.ts +216 -0
- package/src/api/client.metrics.test.ts +106 -0
- package/src/api/client.rightsizing.test.ts +32 -0
- package/src/api/client.ts +2730 -271
- package/src/api/client.yaml.test.ts +45 -0
- package/src/api/diagnose.ts +289 -0
- package/src/api/quotas.ts +16 -0
- package/src/api/rbac.ts +57 -0
- package/src/api/timelineSource.test.ts +217 -0
- package/src/api/timelineSource.ts +582 -0
- package/src/components/ConnectionErrorView.tsx +186 -70
- package/src/components/ContextSwitcher.tsx +63 -18
- package/src/components/DebugOverlay.tsx +5 -3
- package/src/components/NamespaceSwitcher.tsx +41 -0
- package/src/components/UserMenu.tsx +69 -21
- package/src/components/applications/ApplicationsView.tsx +936 -0
- package/src/components/audit/AuditSettingsDialog.tsx +79 -17
- package/src/components/audit/AuditView.tsx +65 -62
- package/src/components/compare/CompareViewRoute.tsx +124 -0
- package/src/components/compare/useCompareCandidates.ts +27 -0
- package/src/components/compare/useCompareLauncher.tsx +79 -0
- package/src/components/cost/ApplicationCostTab.test.ts +204 -0
- package/src/components/cost/ApplicationCostTab.tsx +571 -0
- package/src/components/cost/CostTrendChart.tsx +106 -75
- package/src/components/cost/CostView.test.ts +12 -0
- package/src/components/cost/CostView.tsx +507 -223
- package/src/components/cost/CostViewTabs.test.tsx +21 -0
- package/src/components/cost/CostViewTabs.tsx +40 -0
- package/src/components/cost/CurrentAllocationUse.test.ts +21 -0
- package/src/components/cost/CurrentAllocationUse.tsx +126 -0
- package/src/components/cost/WorkloadCostTab.test.ts +153 -0
- package/src/components/cost/WorkloadCostTab.tsx +372 -0
- package/src/components/cost/cloud-console.test.ts +39 -0
- package/src/components/cost/cloud-console.ts +81 -0
- package/src/components/cost/errors.ts +8 -0
- package/src/components/cost/format.test.ts +27 -0
- package/src/components/cost/format.ts +46 -0
- package/src/components/cost/kinds.ts +5 -0
- package/src/components/curl/ServiceCurlButton.tsx +445 -0
- package/src/components/diagnose/AISettings.tsx +147 -0
- package/src/components/diagnose/DiagnoseContext.tsx +495 -0
- package/src/components/diagnose/DiagnoseSurface.tsx +394 -0
- package/src/components/diagnose/Home.tsx +163 -0
- package/src/components/diagnose/InvestigationView.tsx +622 -0
- package/src/components/diagnose/LocalDiagnoseAction.tsx +162 -0
- package/src/components/diagnose/launch.ts +65 -0
- package/src/components/diagnose/parts.tsx +1756 -0
- package/src/components/dock/BottomDock.tsx +2 -3
- package/src/components/dock/DockContext.tsx +1 -0
- package/src/components/dock/TerminalTab.tsx +1 -1
- package/src/components/dock/WorkloadLogsTab.tsx +21 -5
- package/src/components/dock/index.ts +1 -1
- package/src/components/execution/BatchExecutionView.test.ts +170 -0
- package/src/components/execution/BatchExecutionView.tsx +1329 -0
- package/src/components/execution/batch-run-actions.test.ts +48 -0
- package/src/components/execution/batch-run-actions.ts +24 -0
- package/src/components/execution/batch-timeline.test.ts +57 -0
- package/src/components/execution/batch-timeline.ts +46 -0
- package/src/components/execution/execution-definition.test.ts +208 -0
- package/src/components/execution/execution-definition.ts +245 -0
- package/src/components/gitops/ArgoResourceDiffLoader.tsx +23 -0
- package/src/components/gitops/GitOpsView.tsx +1042 -0
- package/src/components/gitops/RevisionMetaChip.tsx +63 -0
- package/src/components/helm/ChartBrowser.tsx +87 -31
- package/src/components/helm/HelmCompareRoute.tsx +1341 -0
- package/src/components/helm/HelmReleaseDrawer.test.ts +17 -0
- package/src/components/helm/HelmReleaseDrawer.tsx +1073 -102
- package/src/components/helm/HelmView.tsx +237 -96
- package/src/components/helm/InstallWizard.tsx +94 -38
- package/src/components/helm/ManifestDiffViewer.tsx +8 -27
- package/src/components/helm/OwnedResources.tsx +34 -59
- package/src/components/helm/RevisionHistory.tsx +52 -3
- package/src/components/helm/RoleGatedPanel.tsx +3 -3
- package/src/components/helm/TrackChartSourceDialog.tsx +185 -0
- package/src/components/helm/ValuesDiffPreview.tsx +17 -7
- package/src/components/helm/ValuesViewer.tsx +49 -53
- package/src/components/helm/helm-utils.ts +4 -0
- package/src/components/home/ActivitySummary.tsx +4 -1
- package/src/components/home/ClusterHealthCard.tsx +56 -42
- package/src/components/home/CostCard.tsx +21 -36
- package/src/components/home/GitOpsControllersCard.tsx +110 -0
- package/src/components/home/HelmSummary.tsx +3 -1
- package/src/components/home/HomeView.tsx +339 -105
- package/src/components/home/MCPSetupDialog.tsx +29 -87
- package/src/components/home/TrafficSummary.tsx +2 -2
- package/src/components/home/mcpToolCatalog.ts +333 -0
- package/src/components/issues/IssuesPane.tsx +151 -0
- package/src/components/logs/LogsViewer.tsx +4 -1
- package/src/components/logs/ScheduledWorkloadLogsViewer.tsx +135 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +4 -1
- package/src/components/nav/PrimaryNavRail.tsx +285 -0
- package/src/components/portforward/PortForwardButton.tsx +118 -47
- package/src/components/portforward/PortForwardManager.tsx +253 -131
- package/src/components/resource/HPACharts.tsx +237 -0
- package/src/components/resource/PVCUsageBar.tsx +59 -0
- package/src/components/resource/PrometheusCharts.tsx +160 -584
- package/src/components/resource/PrometheusChartsGrid.tsx +270 -0
- package/src/components/resource/RestartChart.tsx +133 -0
- package/src/components/resource/RightsizingStrip.test.ts +109 -0
- package/src/components/resource/RightsizingStrip.tsx +363 -0
- package/src/components/resource-drawer/ResourceDrawer.tsx +3 -1
- package/src/components/resources/CompositeRenderer.tsx +101 -0
- package/src/components/resources/ImageFilesystemModal.tsx +19 -12
- package/src/components/resources/PodFilesystemModal.tsx +6 -5
- package/src/components/resources/ResourceDetailDrawer.tsx +13 -3
- package/src/components/resources/ResourcesView.tsx +194 -17
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +1 -0
- package/src/components/resources/renderers/HPARenderer.tsx +20 -1
- package/src/components/resources/renderers/NamespaceRenderer.tsx +31 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +10 -4
- package/src/components/resources/renderers/PVCRenderer.tsx +19 -1
- package/src/components/resources/renderers/PodRenderer.tsx +30 -6
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +45 -1
- package/src/components/resources/renderers/RoleRenderer.tsx +27 -1
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +28 -1
- package/src/components/resources/renderers/ServiceRenderer.tsx +81 -8
- package/src/components/resources/renderers/WorkloadRenderer.tsx +51 -4
- package/src/components/resources/renderers/index.ts +2 -0
- package/src/components/resources/resource-utils.ts +2 -1
- package/src/components/rightsizing/RightsizingScanView.tsx +938 -0
- package/src/components/rightsizing/copy.test.ts +56 -0
- package/src/components/rightsizing/model.test.ts +227 -0
- package/src/components/rightsizing/model.ts +158 -0
- package/src/components/rightsizing/presentation.test.ts +104 -0
- package/src/components/rightsizing/presentation.ts +94 -0
- package/src/components/settings/MyPermissionsDialog.tsx +241 -0
- package/src/components/settings/SettingsDialog.tsx +1505 -165
- package/src/components/shared/CreateResourceDialog.tsx +9 -2
- package/src/components/shared/LargeClusterNamespacePicker.tsx +3 -3
- package/src/components/timeline/LocalTimelineScrubber.tsx +212 -0
- package/src/components/timeline/RetainedTimelineScrubber.tsx +311 -0
- package/src/components/timeline/TimelineList.tsx +86 -13
- package/src/components/timeline/TimelineSwimlanes.tsx +9 -1299
- package/src/components/timeline/TimelineView.tsx +873 -24
- package/src/components/timeline/TimelineView.urlparams.test.ts +335 -0
- package/src/components/traffic/TrafficFilterSidebar.tsx +10 -45
- package/src/components/traffic/TrafficFlowList.tsx +29 -15
- package/src/components/traffic/TrafficGraph.tsx +42 -24
- package/src/components/traffic/TrafficView.tsx +32 -19
- package/src/components/ui/CommandPalette.tsx +8 -215
- package/src/components/ui/DiagnosticsOverlay.tsx +219 -9
- package/src/components/ui/Markdown.tsx +3 -3
- package/src/components/ui/Omnibar.tsx +602 -0
- package/src/components/ui/RadarOmnibar.tsx +52 -0
- package/src/components/ui/SearchSyntaxHelp.tsx +89 -0
- package/src/components/ui/ShortcutHelpOverlay.tsx +3 -2
- package/src/components/ui/UpdateNotification.tsx +48 -36
- package/src/components/ui/command-items.ts +178 -0
- package/src/components/workload/WorkloadView.tsx +1342 -158
- package/src/context/ConnectionContext.tsx +146 -21
- package/src/context/DiagnoseCustomization.tsx +93 -0
- package/src/context/NavCustomization.tsx +75 -0
- package/src/context/TimelineSource.tsx +50 -0
- package/src/contexts/CapabilitiesContext.tsx +32 -8
- package/src/filter/FilterLocationBridge.tsx +30 -0
- package/src/hooks/useClusterLoadState.ts +73 -0
- package/src/hooks/useDocumentTitle.ts +25 -0
- package/src/hooks/useEventSource.ts +6 -0
- package/src/hooks/useKeyboardShortcuts.tsx +1 -0
- package/src/hooks/useMediaQuery.ts +21 -0
- package/src/hooks/useNavRailPinned.ts +46 -0
- package/src/hooks/useRecentResources.ts +49 -0
- package/src/index.css +162 -1
- package/src/index.ts +73 -1
- package/src/main.tsx +7 -5
- package/src/types/clusterLoadState.ts +33 -0
- package/src/types.ts +2 -0
- package/src/utils/auditBadges.ts +53 -0
- package/src/utils/navigation.ts +64 -1
- package/src/components/ui/NamespaceSelector.tsx +0 -436
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react'
|
|
2
|
+
import { Loader2, Lock, ExternalLink } from 'lucide-react'
|
|
3
|
+
import { clsx } from 'clsx'
|
|
4
|
+
import { useQuery } from '@tanstack/react-query'
|
|
5
|
+
import {
|
|
6
|
+
rbacVerbBadgeClass,
|
|
7
|
+
rbacResourceBadgeClass,
|
|
8
|
+
rbacApiGroupBadgeClass,
|
|
9
|
+
rbacResourceNameBadgeClass,
|
|
10
|
+
rbacNonResourceUrlBadgeClass,
|
|
11
|
+
type RBACWhoamiResponse,
|
|
12
|
+
} from '@skyhook-io/k8s-ui'
|
|
13
|
+
import { useNamespaces, useAuthMe, fetchJSON } from '../../api/client'
|
|
14
|
+
import { useRBACWhoami } from '../../api/rbac'
|
|
15
|
+
|
|
16
|
+
// MyPermissionsContent renders the "your access on this cluster" viewer inline
|
|
17
|
+
// (no modal chrome) so the Settings dialog can host it directly in its content
|
|
18
|
+
// pane. `active` gates the apiserver queries so opening Settings on another
|
|
19
|
+
// section doesn't fire a SelfSubjectRulesReview the user never asked for.
|
|
20
|
+
export function MyPermissionsContent({ active }: { active: boolean }) {
|
|
21
|
+
const [namespace, setNamespace] = useState('default')
|
|
22
|
+
const { data: authMe } = useAuthMe()
|
|
23
|
+
const { data: namespaces } = useNamespaces()
|
|
24
|
+
const { data: whoami, isLoading, error } = useRBACWhoami(namespace, active)
|
|
25
|
+
|
|
26
|
+
// Land on an accessible namespace: once the list loads, if the current pick
|
|
27
|
+
// ('default' initially) isn't among the user's namespaces and they haven't
|
|
28
|
+
// chosen one, switch to the first accessible one — otherwise a user scoped to
|
|
29
|
+
// e.g. team-a would open on an empty 'default'.
|
|
30
|
+
const userPicked = useRef(false)
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (userPicked.current || !namespaces?.length) return
|
|
33
|
+
if (!namespaces.some((ns) => ns.name === namespace)) {
|
|
34
|
+
setNamespace(namespaces[0].name)
|
|
35
|
+
}
|
|
36
|
+
}, [namespaces, namespace])
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="space-y-4">
|
|
40
|
+
{/* Identity + namespace selector */}
|
|
41
|
+
<div className="flex flex-wrap items-end gap-3">
|
|
42
|
+
<div className="flex-1 min-w-[180px]">
|
|
43
|
+
<label className="block text-xs text-theme-text-tertiary mb-1">Identity</label>
|
|
44
|
+
<div className="px-2 py-1.5 text-sm bg-theme-elevated rounded border border-theme-border text-theme-text-primary truncate">
|
|
45
|
+
{authMe?.username || '(current kubeconfig user)'}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="flex-1 min-w-[180px]">
|
|
49
|
+
<label className="block text-xs text-theme-text-tertiary mb-1">Namespace</label>
|
|
50
|
+
<select
|
|
51
|
+
value={namespace}
|
|
52
|
+
onChange={(e) => { userPicked.current = true; setNamespace(e.target.value) }}
|
|
53
|
+
className="w-full px-2 py-1.5 text-sm bg-theme-elevated border border-theme-border rounded text-theme-text-primary focus:outline-none focus:border-blue-500"
|
|
54
|
+
>
|
|
55
|
+
{namespaces?.map((ns) => (
|
|
56
|
+
<option key={ns.name} value={ns.name}>{ns.name}</option>
|
|
57
|
+
))}
|
|
58
|
+
{!namespaces?.some((ns) => ns.name === namespace) && (
|
|
59
|
+
<option value={namespace}>{namespace}</option>
|
|
60
|
+
)}
|
|
61
|
+
</select>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<p className="text-xs text-theme-text-tertiary">
|
|
66
|
+
Computed by the Kubernetes API via{' '}
|
|
67
|
+
<code className="inline-code">SelfSubjectRulesReview</code>.
|
|
68
|
+
Shows what you can do in <span className="text-theme-text-secondary">{namespace}</span>,
|
|
69
|
+
plus any cluster-scoped rules that apply everywhere.
|
|
70
|
+
</p>
|
|
71
|
+
|
|
72
|
+
{whoami?.incomplete && (
|
|
73
|
+
<div className="px-2.5 py-1.5 text-xs bg-amber-500/10 border border-amber-500/20 rounded">
|
|
74
|
+
The apiserver returned an incomplete rule set
|
|
75
|
+
{whoami.evaluationError ? ` (${whoami.evaluationError})` : ''}.
|
|
76
|
+
The list below may understate your actual permissions.
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{isLoading ? (
|
|
81
|
+
<div className="flex items-center gap-2 text-sm text-theme-text-tertiary">
|
|
82
|
+
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
83
|
+
Querying apiserver…
|
|
84
|
+
</div>
|
|
85
|
+
) : error ? (
|
|
86
|
+
<div className="text-sm text-red-400">
|
|
87
|
+
Failed to load: {(error as Error).message}
|
|
88
|
+
</div>
|
|
89
|
+
) : whoami ? (
|
|
90
|
+
<PermissionsTable whoami={whoami} />
|
|
91
|
+
) : null}
|
|
92
|
+
|
|
93
|
+
<RestrictedResources enabled={active} />
|
|
94
|
+
</div>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function PermissionsTable({ whoami }: { whoami: RBACWhoamiResponse }) {
|
|
99
|
+
const resourceRules = whoami.resourceRules ?? []
|
|
100
|
+
const nonResourceRules = whoami.nonResourceRules ?? []
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div className="space-y-4">
|
|
104
|
+
<div>
|
|
105
|
+
<div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-2">
|
|
106
|
+
Resource permissions ({resourceRules.length})
|
|
107
|
+
</div>
|
|
108
|
+
{resourceRules.length === 0 ? (
|
|
109
|
+
<div className="text-sm text-theme-text-tertiary">
|
|
110
|
+
No resource permissions in this namespace.
|
|
111
|
+
</div>
|
|
112
|
+
) : (
|
|
113
|
+
<div className="space-y-2">
|
|
114
|
+
{resourceRules.map((r, i) => (
|
|
115
|
+
<ResourceRuleRow key={i} rule={r} />
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{nonResourceRules.length > 0 && (
|
|
122
|
+
<div>
|
|
123
|
+
<div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-2">
|
|
124
|
+
Non-resource permissions ({nonResourceRules.length})
|
|
125
|
+
</div>
|
|
126
|
+
<div className="space-y-1 text-xs">
|
|
127
|
+
{nonResourceRules.map((r, i) => (
|
|
128
|
+
<div key={i} className="flex items-center gap-1 flex-wrap">
|
|
129
|
+
{(r.verbs ?? []).map((v: string) => (
|
|
130
|
+
<span key={v} className={clsx('badge', rbacVerbBadgeClass(v))}>{v}</span>
|
|
131
|
+
))}
|
|
132
|
+
<span className="text-theme-text-secondary">on</span>
|
|
133
|
+
{(r.nonResourceURLs ?? []).map((u: string) => (
|
|
134
|
+
<span key={u} className={clsx('badge', rbacNonResourceUrlBadgeClass)}>{u}</span>
|
|
135
|
+
))}
|
|
136
|
+
</div>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function ResourceRuleRow({ rule }: { rule: { verbs?: string[]; apiGroups?: string[]; resources?: string[]; resourceNames?: string[] } }) {
|
|
146
|
+
const verbs = rule.verbs ?? []
|
|
147
|
+
const resources = rule.resources ?? []
|
|
148
|
+
const groups = rule.apiGroups ?? []
|
|
149
|
+
const resourceNames = rule.resourceNames ?? []
|
|
150
|
+
return (
|
|
151
|
+
<div className="card-inner text-xs flex items-center gap-1 flex-wrap">
|
|
152
|
+
{verbs.map((v) => (
|
|
153
|
+
<span key={v} className={clsx('badge', rbacVerbBadgeClass(v))}>{v}</span>
|
|
154
|
+
))}
|
|
155
|
+
<span className="text-theme-text-secondary">on</span>
|
|
156
|
+
{resources.length === 0 ? (
|
|
157
|
+
<span className="text-theme-text-secondary italic">(no resources)</span>
|
|
158
|
+
) : (
|
|
159
|
+
resources.map((r) => (
|
|
160
|
+
<span key={r} className={clsx('badge', rbacResourceBadgeClass)}>
|
|
161
|
+
{r === '*' ? '*' : r}
|
|
162
|
+
</span>
|
|
163
|
+
))
|
|
164
|
+
)}
|
|
165
|
+
{groups.length > 0 && groups.some((g) => g !== '') && (
|
|
166
|
+
<>
|
|
167
|
+
<span className="text-theme-text-secondary">in</span>
|
|
168
|
+
{groups.map((g) => (
|
|
169
|
+
<span key={g} className={clsx('badge', rbacApiGroupBadgeClass)}>
|
|
170
|
+
{g === '' ? 'core' : g}
|
|
171
|
+
</span>
|
|
172
|
+
))}
|
|
173
|
+
</>
|
|
174
|
+
)}
|
|
175
|
+
{resourceNames.length > 0 && (
|
|
176
|
+
<>
|
|
177
|
+
<span className="text-theme-text-secondary">named</span>
|
|
178
|
+
{resourceNames.map((n) => (
|
|
179
|
+
<span key={n} className={clsx('badge', rbacResourceNameBadgeClass)}>{n}</span>
|
|
180
|
+
))}
|
|
181
|
+
</>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// displayKind strips the API group from a resource-counts key ("group/Kind" →
|
|
188
|
+
// "Kind"; core kinds have no prefix).
|
|
189
|
+
function displayKind(countKey: string): string {
|
|
190
|
+
const i = countKey.indexOf('/')
|
|
191
|
+
return i === -1 ? countKey : countKey.slice(i + 1)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// RestrictedResources surfaces the kinds Radar isn't showing the user (the
|
|
195
|
+
// resource-counts `forbidden` set) — the "what's hidden from me" half of access,
|
|
196
|
+
// alongside the SelfSubjectRulesReview rules above. That set mixes RBAC denials
|
|
197
|
+
// with not-installed/not-watched kinds, so the copy says "usually RBAC" rather
|
|
198
|
+
// than asserting a cause, and links to the docs that carry the unblock RBAC.
|
|
199
|
+
function RestrictedResources({ enabled }: { enabled: boolean }) {
|
|
200
|
+
const { data } = useQuery<{ forbidden?: string[] }>({
|
|
201
|
+
queryKey: ['resource-counts', 'your-access'],
|
|
202
|
+
queryFn: () => fetchJSON('/resource-counts'),
|
|
203
|
+
enabled,
|
|
204
|
+
staleTime: 10000,
|
|
205
|
+
})
|
|
206
|
+
const forbidden = data?.forbidden ?? []
|
|
207
|
+
if (forbidden.length === 0) return null
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<div>
|
|
211
|
+
<div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-2 flex items-center gap-1.5">
|
|
212
|
+
<Lock className="w-3.5 h-3.5 text-amber-400" />
|
|
213
|
+
Restricted or unavailable ({forbidden.length})
|
|
214
|
+
</div>
|
|
215
|
+
<p className="text-xs text-theme-text-tertiary mb-2">
|
|
216
|
+
Resource types Radar isn't showing you — usually because your RBAC doesn't allow listing
|
|
217
|
+
them, sometimes because the type isn't installed or watched on this cluster. Either way,
|
|
218
|
+
not an empty cluster.
|
|
219
|
+
</p>
|
|
220
|
+
<div className="flex flex-wrap gap-1.5">
|
|
221
|
+
{forbidden.map((k) => (
|
|
222
|
+
<span
|
|
223
|
+
key={k}
|
|
224
|
+
className="inline-flex items-center px-2 py-0.5 text-xs rounded border border-theme-border bg-theme-elevated text-theme-text-secondary"
|
|
225
|
+
>
|
|
226
|
+
{displayKind(k)}
|
|
227
|
+
</span>
|
|
228
|
+
))}
|
|
229
|
+
</div>
|
|
230
|
+
<a
|
|
231
|
+
href="https://radarhq.io/docs/cloud/rbac"
|
|
232
|
+
target="_blank"
|
|
233
|
+
rel="noreferrer"
|
|
234
|
+
className="inline-flex items-center gap-1 text-xs text-accent-text hover:underline mt-2"
|
|
235
|
+
>
|
|
236
|
+
How to get access
|
|
237
|
+
<ExternalLink className="w-3 h-3" />
|
|
238
|
+
</a>
|
|
239
|
+
</div>
|
|
240
|
+
)
|
|
241
|
+
}
|