@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.
Files changed (177) hide show
  1. package/README.md +7 -1
  2. package/package.json +33 -25
  3. package/src/App.tsx +1449 -382
  4. package/src/RadarApp.tsx +132 -19
  5. package/src/api/apiResources.ts +1 -1
  6. package/src/api/client.argoResourceSync.test.ts +69 -0
  7. package/src/api/client.delta.test.ts +89 -0
  8. package/src/api/client.deltaSync.test.ts +216 -0
  9. package/src/api/client.metrics.test.ts +106 -0
  10. package/src/api/client.rightsizing.test.ts +32 -0
  11. package/src/api/client.ts +2730 -271
  12. package/src/api/client.yaml.test.ts +45 -0
  13. package/src/api/diagnose.ts +289 -0
  14. package/src/api/quotas.ts +16 -0
  15. package/src/api/rbac.ts +57 -0
  16. package/src/api/timelineSource.test.ts +217 -0
  17. package/src/api/timelineSource.ts +582 -0
  18. package/src/components/ConnectionErrorView.tsx +186 -70
  19. package/src/components/ContextSwitcher.tsx +63 -18
  20. package/src/components/DebugOverlay.tsx +5 -3
  21. package/src/components/NamespaceSwitcher.tsx +41 -0
  22. package/src/components/UserMenu.tsx +69 -21
  23. package/src/components/applications/ApplicationsView.tsx +936 -0
  24. package/src/components/audit/AuditSettingsDialog.tsx +79 -17
  25. package/src/components/audit/AuditView.tsx +65 -62
  26. package/src/components/compare/CompareViewRoute.tsx +124 -0
  27. package/src/components/compare/useCompareCandidates.ts +27 -0
  28. package/src/components/compare/useCompareLauncher.tsx +79 -0
  29. package/src/components/cost/ApplicationCostTab.test.ts +204 -0
  30. package/src/components/cost/ApplicationCostTab.tsx +571 -0
  31. package/src/components/cost/CostTrendChart.tsx +106 -75
  32. package/src/components/cost/CostView.test.ts +12 -0
  33. package/src/components/cost/CostView.tsx +507 -223
  34. package/src/components/cost/CostViewTabs.test.tsx +21 -0
  35. package/src/components/cost/CostViewTabs.tsx +40 -0
  36. package/src/components/cost/CurrentAllocationUse.test.ts +21 -0
  37. package/src/components/cost/CurrentAllocationUse.tsx +126 -0
  38. package/src/components/cost/WorkloadCostTab.test.ts +153 -0
  39. package/src/components/cost/WorkloadCostTab.tsx +372 -0
  40. package/src/components/cost/cloud-console.test.ts +39 -0
  41. package/src/components/cost/cloud-console.ts +81 -0
  42. package/src/components/cost/errors.ts +8 -0
  43. package/src/components/cost/format.test.ts +27 -0
  44. package/src/components/cost/format.ts +46 -0
  45. package/src/components/cost/kinds.ts +5 -0
  46. package/src/components/curl/ServiceCurlButton.tsx +445 -0
  47. package/src/components/diagnose/AISettings.tsx +147 -0
  48. package/src/components/diagnose/DiagnoseContext.tsx +495 -0
  49. package/src/components/diagnose/DiagnoseSurface.tsx +394 -0
  50. package/src/components/diagnose/Home.tsx +163 -0
  51. package/src/components/diagnose/InvestigationView.tsx +622 -0
  52. package/src/components/diagnose/LocalDiagnoseAction.tsx +162 -0
  53. package/src/components/diagnose/launch.ts +65 -0
  54. package/src/components/diagnose/parts.tsx +1756 -0
  55. package/src/components/dock/BottomDock.tsx +2 -3
  56. package/src/components/dock/DockContext.tsx +1 -0
  57. package/src/components/dock/TerminalTab.tsx +1 -1
  58. package/src/components/dock/WorkloadLogsTab.tsx +21 -5
  59. package/src/components/dock/index.ts +1 -1
  60. package/src/components/execution/BatchExecutionView.test.ts +170 -0
  61. package/src/components/execution/BatchExecutionView.tsx +1329 -0
  62. package/src/components/execution/batch-run-actions.test.ts +48 -0
  63. package/src/components/execution/batch-run-actions.ts +24 -0
  64. package/src/components/execution/batch-timeline.test.ts +57 -0
  65. package/src/components/execution/batch-timeline.ts +46 -0
  66. package/src/components/execution/execution-definition.test.ts +208 -0
  67. package/src/components/execution/execution-definition.ts +245 -0
  68. package/src/components/gitops/ArgoResourceDiffLoader.tsx +23 -0
  69. package/src/components/gitops/GitOpsView.tsx +1042 -0
  70. package/src/components/gitops/RevisionMetaChip.tsx +63 -0
  71. package/src/components/helm/ChartBrowser.tsx +87 -31
  72. package/src/components/helm/HelmCompareRoute.tsx +1341 -0
  73. package/src/components/helm/HelmReleaseDrawer.test.ts +17 -0
  74. package/src/components/helm/HelmReleaseDrawer.tsx +1073 -102
  75. package/src/components/helm/HelmView.tsx +237 -96
  76. package/src/components/helm/InstallWizard.tsx +94 -38
  77. package/src/components/helm/ManifestDiffViewer.tsx +8 -27
  78. package/src/components/helm/OwnedResources.tsx +34 -59
  79. package/src/components/helm/RevisionHistory.tsx +52 -3
  80. package/src/components/helm/RoleGatedPanel.tsx +3 -3
  81. package/src/components/helm/TrackChartSourceDialog.tsx +185 -0
  82. package/src/components/helm/ValuesDiffPreview.tsx +17 -7
  83. package/src/components/helm/ValuesViewer.tsx +49 -53
  84. package/src/components/helm/helm-utils.ts +4 -0
  85. package/src/components/home/ActivitySummary.tsx +4 -1
  86. package/src/components/home/ClusterHealthCard.tsx +56 -42
  87. package/src/components/home/CostCard.tsx +21 -36
  88. package/src/components/home/GitOpsControllersCard.tsx +110 -0
  89. package/src/components/home/HelmSummary.tsx +3 -1
  90. package/src/components/home/HomeView.tsx +339 -105
  91. package/src/components/home/MCPSetupDialog.tsx +29 -87
  92. package/src/components/home/TrafficSummary.tsx +2 -2
  93. package/src/components/home/mcpToolCatalog.ts +333 -0
  94. package/src/components/issues/IssuesPane.tsx +151 -0
  95. package/src/components/logs/LogsViewer.tsx +4 -1
  96. package/src/components/logs/ScheduledWorkloadLogsViewer.tsx +135 -0
  97. package/src/components/logs/WorkloadLogsViewer.tsx +4 -1
  98. package/src/components/nav/PrimaryNavRail.tsx +285 -0
  99. package/src/components/portforward/PortForwardButton.tsx +118 -47
  100. package/src/components/portforward/PortForwardManager.tsx +253 -131
  101. package/src/components/resource/HPACharts.tsx +237 -0
  102. package/src/components/resource/PVCUsageBar.tsx +59 -0
  103. package/src/components/resource/PrometheusCharts.tsx +160 -584
  104. package/src/components/resource/PrometheusChartsGrid.tsx +270 -0
  105. package/src/components/resource/RestartChart.tsx +133 -0
  106. package/src/components/resource/RightsizingStrip.test.ts +109 -0
  107. package/src/components/resource/RightsizingStrip.tsx +363 -0
  108. package/src/components/resource-drawer/ResourceDrawer.tsx +3 -1
  109. package/src/components/resources/CompositeRenderer.tsx +101 -0
  110. package/src/components/resources/ImageFilesystemModal.tsx +19 -12
  111. package/src/components/resources/PodFilesystemModal.tsx +6 -5
  112. package/src/components/resources/ResourceDetailDrawer.tsx +13 -3
  113. package/src/components/resources/ResourcesView.tsx +194 -17
  114. package/src/components/resources/renderers/CronWorkflowRenderer.tsx +1 -0
  115. package/src/components/resources/renderers/HPARenderer.tsx +20 -1
  116. package/src/components/resources/renderers/NamespaceRenderer.tsx +31 -0
  117. package/src/components/resources/renderers/NodeRenderer.tsx +10 -4
  118. package/src/components/resources/renderers/PVCRenderer.tsx +19 -1
  119. package/src/components/resources/renderers/PodRenderer.tsx +30 -6
  120. package/src/components/resources/renderers/RoleBindingRenderer.tsx +45 -1
  121. package/src/components/resources/renderers/RoleRenderer.tsx +27 -1
  122. package/src/components/resources/renderers/ServiceAccountRenderer.tsx +28 -1
  123. package/src/components/resources/renderers/ServiceRenderer.tsx +81 -8
  124. package/src/components/resources/renderers/WorkloadRenderer.tsx +51 -4
  125. package/src/components/resources/renderers/index.ts +2 -0
  126. package/src/components/resources/resource-utils.ts +2 -1
  127. package/src/components/rightsizing/RightsizingScanView.tsx +938 -0
  128. package/src/components/rightsizing/copy.test.ts +56 -0
  129. package/src/components/rightsizing/model.test.ts +227 -0
  130. package/src/components/rightsizing/model.ts +158 -0
  131. package/src/components/rightsizing/presentation.test.ts +104 -0
  132. package/src/components/rightsizing/presentation.ts +94 -0
  133. package/src/components/settings/MyPermissionsDialog.tsx +241 -0
  134. package/src/components/settings/SettingsDialog.tsx +1505 -165
  135. package/src/components/shared/CreateResourceDialog.tsx +9 -2
  136. package/src/components/shared/LargeClusterNamespacePicker.tsx +3 -3
  137. package/src/components/timeline/LocalTimelineScrubber.tsx +212 -0
  138. package/src/components/timeline/RetainedTimelineScrubber.tsx +311 -0
  139. package/src/components/timeline/TimelineList.tsx +86 -13
  140. package/src/components/timeline/TimelineSwimlanes.tsx +9 -1299
  141. package/src/components/timeline/TimelineView.tsx +873 -24
  142. package/src/components/timeline/TimelineView.urlparams.test.ts +335 -0
  143. package/src/components/traffic/TrafficFilterSidebar.tsx +10 -45
  144. package/src/components/traffic/TrafficFlowList.tsx +29 -15
  145. package/src/components/traffic/TrafficGraph.tsx +42 -24
  146. package/src/components/traffic/TrafficView.tsx +32 -19
  147. package/src/components/ui/CommandPalette.tsx +8 -215
  148. package/src/components/ui/DiagnosticsOverlay.tsx +219 -9
  149. package/src/components/ui/Markdown.tsx +3 -3
  150. package/src/components/ui/Omnibar.tsx +602 -0
  151. package/src/components/ui/RadarOmnibar.tsx +52 -0
  152. package/src/components/ui/SearchSyntaxHelp.tsx +89 -0
  153. package/src/components/ui/ShortcutHelpOverlay.tsx +3 -2
  154. package/src/components/ui/UpdateNotification.tsx +48 -36
  155. package/src/components/ui/command-items.ts +178 -0
  156. package/src/components/workload/WorkloadView.tsx +1342 -158
  157. package/src/context/ConnectionContext.tsx +146 -21
  158. package/src/context/DiagnoseCustomization.tsx +93 -0
  159. package/src/context/NavCustomization.tsx +75 -0
  160. package/src/context/TimelineSource.tsx +50 -0
  161. package/src/contexts/CapabilitiesContext.tsx +32 -8
  162. package/src/filter/FilterLocationBridge.tsx +30 -0
  163. package/src/hooks/useClusterLoadState.ts +73 -0
  164. package/src/hooks/useDocumentTitle.ts +25 -0
  165. package/src/hooks/useEventSource.ts +6 -0
  166. package/src/hooks/useKeyboardShortcuts.tsx +1 -0
  167. package/src/hooks/useMediaQuery.ts +21 -0
  168. package/src/hooks/useNavRailPinned.ts +46 -0
  169. package/src/hooks/useRecentResources.ts +49 -0
  170. package/src/index.css +162 -1
  171. package/src/index.ts +73 -1
  172. package/src/main.tsx +7 -5
  173. package/src/types/clusterLoadState.ts +33 -0
  174. package/src/types.ts +2 -0
  175. package/src/utils/auditBadges.ts +53 -0
  176. package/src/utils/navigation.ts +64 -1
  177. 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
+ }