@skyhook-io/radar-app 0.1.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 (251) hide show
  1. package/README.md +67 -0
  2. package/package.json +80 -0
  3. package/src/App.tsx +1538 -0
  4. package/src/RadarApp.tsx +145 -0
  5. package/src/api/apiResources.ts +28 -0
  6. package/src/api/client.ts +2583 -0
  7. package/src/api/config.ts +116 -0
  8. package/src/api/traffic.ts +139 -0
  9. package/src/components/ConnectionErrorView.tsx +272 -0
  10. package/src/components/ContextSwitcher.tsx +481 -0
  11. package/src/components/DebugOverlay.tsx +94 -0
  12. package/src/components/UserMenu.tsx +87 -0
  13. package/src/components/audit/AuditSettingsDialog.tsx +162 -0
  14. package/src/components/audit/AuditView.tsx +123 -0
  15. package/src/components/cost/CostTrendChart.tsx +388 -0
  16. package/src/components/cost/CostView.tsx +545 -0
  17. package/src/components/dock/BottomDock.tsx +96 -0
  18. package/src/components/dock/DockContext.tsx +11 -0
  19. package/src/components/dock/LocalTerminalTab.tsx +22 -0
  20. package/src/components/dock/LogsTab.tsx +26 -0
  21. package/src/components/dock/NodeTerminalTab.tsx +50 -0
  22. package/src/components/dock/TerminalTab.tsx +42 -0
  23. package/src/components/dock/TrafficFlowListTab.tsx +18 -0
  24. package/src/components/dock/WorkloadLogsTab.tsx +23 -0
  25. package/src/components/dock/index.ts +2 -0
  26. package/src/components/gitops/GitOpsActions.tsx +1 -0
  27. package/src/components/gitops/GitOpsStatusBadge.tsx +1 -0
  28. package/src/components/gitops/ManagedResourcesList.tsx +1 -0
  29. package/src/components/gitops/SyncCountdown.tsx +1 -0
  30. package/src/components/gitops/index.ts +4 -0
  31. package/src/components/helm/ChartBrowser.tsx +580 -0
  32. package/src/components/helm/HelmReleaseDrawer.tsx +774 -0
  33. package/src/components/helm/HelmView.tsx +475 -0
  34. package/src/components/helm/InstallWizard.tsx +1060 -0
  35. package/src/components/helm/ManifestDiffViewer.tsx +91 -0
  36. package/src/components/helm/ManifestViewer.tsx +61 -0
  37. package/src/components/helm/OwnedResources.tsx +465 -0
  38. package/src/components/helm/RevisionHistory.tsx +167 -0
  39. package/src/components/helm/ValuesDiffPreview.tsx +190 -0
  40. package/src/components/helm/ValuesViewer.tsx +365 -0
  41. package/src/components/helm/helm-utils.ts +37 -0
  42. package/src/components/home/ActivitySummary.tsx +262 -0
  43. package/src/components/home/CertificateHealthCard.tsx +105 -0
  44. package/src/components/home/ClusterHealthCard.tsx +483 -0
  45. package/src/components/home/CostCard.tsx +112 -0
  46. package/src/components/home/HealthRing.tsx +1 -0
  47. package/src/components/home/HelmSummary.tsx +129 -0
  48. package/src/components/home/HomeView.tsx +224 -0
  49. package/src/components/home/MCPSetupDialog.tsx +417 -0
  50. package/src/components/home/NetworkPolicyCoverageCard.tsx +109 -0
  51. package/src/components/home/TopologyPreview.tsx +219 -0
  52. package/src/components/home/TrafficSummary.tsx +154 -0
  53. package/src/components/logs/JsonLogLine.tsx +1 -0
  54. package/src/components/logs/LogCore.tsx +2 -0
  55. package/src/components/logs/LogsViewer.tsx +44 -0
  56. package/src/components/logs/WorkloadLogsViewer.tsx +40 -0
  57. package/src/components/logs/useLogBuffer.ts +2 -0
  58. package/src/components/logs/useLogSearch.ts +1 -0
  59. package/src/components/portforward/PortForwardButton.tsx +375 -0
  60. package/src/components/portforward/PortForwardManager.tsx +871 -0
  61. package/src/components/resource/PrometheusCharts.tsx +687 -0
  62. package/src/components/resource-drawer/ResourceDrawer.tsx +214 -0
  63. package/src/components/resources/ImageFilesystemModal.tsx +745 -0
  64. package/src/components/resources/PodFilesystemModal.tsx +407 -0
  65. package/src/components/resources/ResourceDetailDrawer.tsx +43 -0
  66. package/src/components/resources/ResourcesView.tsx +190 -0
  67. package/src/components/resources/drawer-components.tsx +1 -0
  68. package/src/components/resources/file-browser-utils.ts +35 -0
  69. package/src/components/resources/renderers/AlertRenderer.tsx +1 -0
  70. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +17 -0
  71. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +1 -0
  72. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +1 -0
  73. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +1 -0
  74. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +1 -0
  75. package/src/components/resources/renderers/CertificateRenderer.tsx +1 -0
  76. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +1 -0
  77. package/src/components/resources/renderers/ChallengeRenderer.tsx +1 -0
  78. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +1 -0
  79. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +1 -0
  80. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +1 -0
  81. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +1 -0
  82. package/src/components/resources/renderers/ConfigMapRenderer.tsx +1 -0
  83. package/src/components/resources/renderers/CronJobRenderer.tsx +1 -0
  84. package/src/components/resources/renderers/EventRenderer.tsx +1 -0
  85. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +1 -0
  86. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +1 -0
  87. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +1 -0
  88. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +1 -0
  89. package/src/components/resources/renderers/GatewayClassRenderer.tsx +1 -0
  90. package/src/components/resources/renderers/GatewayRenderer.tsx +1 -0
  91. package/src/components/resources/renderers/GenericRenderer.tsx +1 -0
  92. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +1 -0
  93. package/src/components/resources/renderers/HPARenderer.tsx +1 -0
  94. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +1 -0
  95. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +1 -0
  96. package/src/components/resources/renderers/IngressClassRenderer.tsx +1 -0
  97. package/src/components/resources/renderers/IngressRenderer.tsx +1 -0
  98. package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +1 -0
  99. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +1 -0
  100. package/src/components/resources/renderers/IstioGatewayRenderer.tsx +1 -0
  101. package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +1 -0
  102. package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +1 -0
  103. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +1 -0
  104. package/src/components/resources/renderers/JobRenderer.tsx +1 -0
  105. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +1 -0
  106. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +1 -0
  107. package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +1 -0
  108. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +1 -0
  109. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +1 -0
  110. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +1 -0
  111. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +1 -0
  112. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +1 -0
  113. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +1 -0
  114. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +1 -0
  115. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +1 -0
  116. package/src/components/resources/renderers/KnativeRouteRenderer.tsx +1 -0
  117. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +1 -0
  118. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +1 -0
  119. package/src/components/resources/renderers/KustomizationRenderer.tsx +1 -0
  120. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +1 -0
  121. package/src/components/resources/renderers/LeaseRenderer.tsx +1 -0
  122. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +1 -0
  123. package/src/components/resources/renderers/NodeRenderer.tsx +44 -0
  124. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +1 -0
  125. package/src/components/resources/renderers/OrderRenderer.tsx +1 -0
  126. package/src/components/resources/renderers/PVCRenderer.tsx +1 -0
  127. package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +1 -0
  128. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +1 -0
  129. package/src/components/resources/renderers/PodMonitorRenderer.tsx +1 -0
  130. package/src/components/resources/renderers/PodRenderer.tsx +94 -0
  131. package/src/components/resources/renderers/PriorityClassRenderer.tsx +1 -0
  132. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +1 -0
  133. package/src/components/resources/renderers/ReplicaSetRenderer.tsx +1 -0
  134. package/src/components/resources/renderers/RoleBindingRenderer.tsx +1 -0
  135. package/src/components/resources/renderers/RoleRenderer.tsx +1 -0
  136. package/src/components/resources/renderers/RolloutRenderer.tsx +1 -0
  137. package/src/components/resources/renderers/RuntimeClassRenderer.tsx +1 -0
  138. package/src/components/resources/renderers/SbomReportRenderer.tsx +1 -0
  139. package/src/components/resources/renderers/SealedSecretRenderer.tsx +1 -0
  140. package/src/components/resources/renderers/SecretRenderer.tsx +1 -0
  141. package/src/components/resources/renderers/SecretStoreRenderer.tsx +1 -0
  142. package/src/components/resources/renderers/ServiceAccountRenderer.tsx +1 -0
  143. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +1 -0
  144. package/src/components/resources/renderers/ServiceRenderer.tsx +26 -0
  145. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +1 -0
  146. package/src/components/resources/renderers/StorageClassRenderer.tsx +1 -0
  147. package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +1 -0
  148. package/src/components/resources/renderers/VPARenderer.tsx +1 -0
  149. package/src/components/resources/renderers/VeleroBSLRenderer.tsx +1 -0
  150. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +1 -0
  151. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +1 -0
  152. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +1 -0
  153. package/src/components/resources/renderers/VeleroVSLRenderer.tsx +1 -0
  154. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +1 -0
  155. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +1 -0
  156. package/src/components/resources/renderers/WorkflowRenderer.tsx +1 -0
  157. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +1 -0
  158. package/src/components/resources/renderers/WorkloadRenderer.tsx +52 -0
  159. package/src/components/resources/renderers/argo-cells.tsx +1 -0
  160. package/src/components/resources/renderers/certmanager-cells.tsx +1 -0
  161. package/src/components/resources/renderers/cnpg-cells.tsx +1 -0
  162. package/src/components/resources/renderers/eso-cells.tsx +1 -0
  163. package/src/components/resources/renderers/flux-cells.tsx +1 -0
  164. package/src/components/resources/renderers/index.ts +91 -0
  165. package/src/components/resources/renderers/istio-cells.tsx +1 -0
  166. package/src/components/resources/renderers/karpenter-cells.tsx +1 -0
  167. package/src/components/resources/renderers/keda-cells.tsx +1 -0
  168. package/src/components/resources/renderers/knative-cells.tsx +1 -0
  169. package/src/components/resources/renderers/kyverno-cells.tsx +1 -0
  170. package/src/components/resources/renderers/prometheus-cells.tsx +1 -0
  171. package/src/components/resources/renderers/traefik-cells.tsx +1 -0
  172. package/src/components/resources/renderers/trivy-cells.tsx +1 -0
  173. package/src/components/resources/renderers/trivy-shared.tsx +1 -0
  174. package/src/components/resources/renderers/velero-cells.tsx +1 -0
  175. package/src/components/resources/resource-utils-argo.ts +2 -0
  176. package/src/components/resources/resource-utils-certmanager.ts +2 -0
  177. package/src/components/resources/resource-utils-cnpg.ts +2 -0
  178. package/src/components/resources/resource-utils-eso.ts +2 -0
  179. package/src/components/resources/resource-utils-flux.ts +2 -0
  180. package/src/components/resources/resource-utils-istio.ts +2 -0
  181. package/src/components/resources/resource-utils-karpenter.ts +2 -0
  182. package/src/components/resources/resource-utils-keda.ts +2 -0
  183. package/src/components/resources/resource-utils-knative.ts +2 -0
  184. package/src/components/resources/resource-utils-kyverno.ts +2 -0
  185. package/src/components/resources/resource-utils-prometheus.ts +2 -0
  186. package/src/components/resources/resource-utils-traefik.ts +1 -0
  187. package/src/components/resources/resource-utils-trivy.ts +2 -0
  188. package/src/components/resources/resource-utils-velero.ts +2 -0
  189. package/src/components/resources/resource-utils.ts +5 -0
  190. package/src/components/settings/SettingsDialog.tsx +537 -0
  191. package/src/components/shared/CreateResourceDialog.tsx +17 -0
  192. package/src/components/shared/EditableYamlView.tsx +24 -0
  193. package/src/components/shared/LargeClusterNamespacePicker.tsx +70 -0
  194. package/src/components/shared/ResourceRendererDispatch.tsx +31 -0
  195. package/src/components/timeline/DiffViewer.tsx +1 -0
  196. package/src/components/timeline/TimelineList.tsx +69 -0
  197. package/src/components/timeline/TimelineSwimlanes.tsx +1308 -0
  198. package/src/components/timeline/TimelineView.tsx +157 -0
  199. package/src/components/timeline/shared.tsx +1 -0
  200. package/src/components/traffic/TrafficFilterSidebar.tsx +571 -0
  201. package/src/components/traffic/TrafficFlowList.tsx +415 -0
  202. package/src/components/traffic/TrafficFlowListContext.tsx +68 -0
  203. package/src/components/traffic/TrafficGraph.tsx +1546 -0
  204. package/src/components/traffic/TrafficView.tsx +1213 -0
  205. package/src/components/traffic/TrafficWizard.tsx +386 -0
  206. package/src/components/traffic/index.ts +3 -0
  207. package/src/components/ui/CodeViewer.tsx +8 -0
  208. package/src/components/ui/CommandPalette.tsx +460 -0
  209. package/src/components/ui/ConfirmDialog.tsx +1 -0
  210. package/src/components/ui/DiagnosticsOverlay.tsx +619 -0
  211. package/src/components/ui/ErrorBoundary.tsx +46 -0
  212. package/src/components/ui/ForceDeleteConfirmDialog.tsx +1 -0
  213. package/src/components/ui/Markdown.tsx +108 -0
  214. package/src/components/ui/MetricsChart.tsx +1 -0
  215. package/src/components/ui/NamespaceSelector.tsx +436 -0
  216. package/src/components/ui/ResourceBar.tsx +1 -0
  217. package/src/components/ui/ShortcutHelpOverlay.tsx +301 -0
  218. package/src/components/ui/Toast.tsx +1 -0
  219. package/src/components/ui/Tooltip.tsx +1 -0
  220. package/src/components/ui/UpdateNotification.tsx +299 -0
  221. package/src/components/ui/YamlEditor.tsx +1 -0
  222. package/src/components/workload/WorkloadView.tsx +532 -0
  223. package/src/context/ConnectionContext.tsx +173 -0
  224. package/src/context/ContextSwitchContext.tsx +56 -0
  225. package/src/context/NavCustomization.tsx +62 -0
  226. package/src/context/ThemeContext.tsx +97 -0
  227. package/src/contexts/CapabilitiesContext.tsx +130 -0
  228. package/src/hooks/useAnimatedUnmount.ts +1 -0
  229. package/src/hooks/useDesktopDownload.ts +41 -0
  230. package/src/hooks/useEventSource.ts +262 -0
  231. package/src/hooks/useFavorites.ts +69 -0
  232. package/src/hooks/useKeyboardShortcuts.tsx +7 -0
  233. package/src/hooks/useRefreshAnimation.ts +1 -0
  234. package/src/index.css +243 -0
  235. package/src/index.ts +17 -0
  236. package/src/main.tsx +158 -0
  237. package/src/types/gitops.ts +2 -0
  238. package/src/types.ts +3 -0
  239. package/src/utils/animation.ts +2 -0
  240. package/src/utils/badge-colors.ts +2 -0
  241. package/src/utils/context-name.ts +2 -0
  242. package/src/utils/desktop-download.ts +66 -0
  243. package/src/utils/desktop-open-folder.ts +21 -0
  244. package/src/utils/format.ts +2 -0
  245. package/src/utils/log-format.ts +12 -0
  246. package/src/utils/navigation.ts +23 -0
  247. package/src/utils/resource-hierarchy.ts +2 -0
  248. package/src/utils/resource-icons.ts +2 -0
  249. package/src/utils/skeleton-yaml.ts +2 -0
  250. package/src/utils/traffic-colors.ts +54 -0
  251. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,116 @@
1
+ // Runtime configuration for Radar's frontend.
2
+ //
3
+ // When Radar runs as its own binary (standalone or in-cluster), the
4
+ // frontend is same-origin with the Go API server and `apiBase` is `/api`.
5
+ // When Radar's web is embedded as `@skyhook-io/radar-app` in another
6
+ // frontend (e.g. Radar Hub), the host app calls `setApiBase()` to point
7
+ // at a cluster-scoped proxy URL like `/c/{cluster_id}/api` before mounting.
8
+ //
9
+ // This module is deliberately a small mutable singleton rather than a
10
+ // React context so it works in non-React code paths (EventSource,
11
+ // WebSocket URL construction) without threading props through every
12
+ // call site. Multi-instance hosting (mounting two Radar apps for two
13
+ // clusters on the same page) is a V2 concern — it would require
14
+ // replacing this with a context.
15
+
16
+ let apiBase = '/api';
17
+ let basename = '';
18
+ let authHeadersProvider: () => Record<string, string> = () => ({});
19
+ let credentialsMode: RequestCredentials = 'same-origin';
20
+
21
+ export function getApiBase(): string {
22
+ return apiBase;
23
+ }
24
+
25
+ /**
26
+ * Sets the base URL used for REST API, SSE, and WebSocket requests.
27
+ * Call before mounting the Radar app. Trailing slashes are stripped.
28
+ *
29
+ * Accepts either:
30
+ * - A relative path: `/api`, `/c/abc/api` (URLs derive scheme + host from window.location)
31
+ * - An absolute URL: `https://api.radar.skyhook.io/c/abc/api` (URLs use that origin)
32
+ */
33
+ export function setApiBase(url: string): void {
34
+ apiBase = url.replace(/\/+$/, '');
35
+ }
36
+
37
+ /**
38
+ * Returns the router basename (e.g. `/c/abc` when mounted in Radar Hub),
39
+ * or `''` for standalone use.
40
+ */
41
+ export function getBasename(): string {
42
+ return basename;
43
+ }
44
+
45
+ /**
46
+ * Sets the router basename. React Router strips this from location.pathname
47
+ * inside the app, so all internal paths remain relative to `/`. Used by
48
+ * full-page navigation helpers (auth redirects, etc.) where we still need
49
+ * to construct a URL relative to the host app's origin.
50
+ */
51
+ export function setBasename(value: string): void {
52
+ basename = value.replace(/\/+$/, '');
53
+ }
54
+
55
+ /**
56
+ * Builds an absolute path under the configured basename — used for full
57
+ * window.location navigation (OIDC login redirect, return-path tracking)
58
+ * where React Router doesn't apply.
59
+ */
60
+ export function routePath(path: string): string {
61
+ const prefix = basename;
62
+ if (!prefix) return path;
63
+ if (path.startsWith(prefix + '/') || path === prefix) return path;
64
+ return prefix + path;
65
+ }
66
+
67
+ /**
68
+ * Builds a WebSocket URL for a given API path.
69
+ *
70
+ * When apiBase is relative, uses window.location.host + apiBase + path.
71
+ * When apiBase is absolute, swaps http(s) → ws(s) and appends path.
72
+ */
73
+ export function getWsUrl(path: string): string {
74
+ const base = apiBase;
75
+ if (base.startsWith('http://') || base.startsWith('https://')) {
76
+ return base.replace(/^http/, 'ws') + path;
77
+ }
78
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
79
+ return `${protocol}//${window.location.host}${base}${path}`;
80
+ }
81
+
82
+ /** Convenience: `${apiBase}${path}` for fetch call sites. */
83
+ export function apiUrl(path: string): string {
84
+ return getApiBase() + path;
85
+ }
86
+
87
+ /**
88
+ * Register a function that returns HTTP headers to attach to every Radar
89
+ * API request. Used by library consumers (e.g. Radar Hub) to inject
90
+ * Authorization bearer tokens or session tokens. The provider is called
91
+ * on each request so it can read fresh state.
92
+ *
93
+ * Standalone Radar leaves this empty — auth is cookie-based in OIDC or
94
+ * proxy mode, no explicit headers needed.
95
+ */
96
+ export function setAuthHeadersProvider(fn: () => Record<string, string>): void {
97
+ authHeadersProvider = fn;
98
+ }
99
+
100
+ /** Internal: returns the registered auth headers for fetch() calls. */
101
+ export function getAuthHeaders(): Record<string, string> {
102
+ return authHeadersProvider();
103
+ }
104
+
105
+ /**
106
+ * Sets the `credentials` mode for all Radar fetches. Default 'same-origin'
107
+ * (standalone Radar binary). Library consumers at a different origin should
108
+ * set 'include' so browser cookies + CORS preflight cooperate.
109
+ */
110
+ export function setCredentialsMode(mode: RequestCredentials): void {
111
+ credentialsMode = mode;
112
+ }
113
+
114
+ export function getCredentialsMode(): RequestCredentials {
115
+ return credentialsMode;
116
+ }
@@ -0,0 +1,139 @@
1
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
2
+ import type { TrafficSourcesResponse, TrafficFlowsResponse } from '../types'
3
+ import { apiUrl, getAuthHeaders, getCredentialsMode } from './config'
4
+
5
+ async function fetchJSON<T>(path: string): Promise<T> {
6
+ const response = await fetch(apiUrl(path), {
7
+ credentials: getCredentialsMode(),
8
+ headers: getAuthHeaders(),
9
+ })
10
+ if (!response.ok) {
11
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
12
+ throw new Error(error.error || `HTTP ${response.status}`)
13
+ }
14
+ return response.json()
15
+ }
16
+
17
+ // Connection info returned by connect endpoint
18
+ export interface TrafficConnectionInfo {
19
+ connected: boolean
20
+ localPort?: number
21
+ address?: string
22
+ namespace?: string
23
+ serviceName?: string
24
+ contextName?: string
25
+ error?: string
26
+ }
27
+
28
+ // Get available traffic sources and recommendations
29
+ export function useTrafficSources() {
30
+ return useQuery<TrafficSourcesResponse>({
31
+ queryKey: ['traffic-sources'],
32
+ queryFn: () => fetchJSON('/traffic/sources'),
33
+ staleTime: 30000, // 30 seconds
34
+ retry: 1,
35
+ })
36
+ }
37
+
38
+ // Get traffic flows
39
+ export interface UseTrafficFlowsOptions {
40
+ namespaces?: string[]
41
+ since?: string // Duration like "5m", "1h"
42
+ enabled?: boolean
43
+ }
44
+
45
+ export function useTrafficFlows(options: UseTrafficFlowsOptions = {}) {
46
+ const { namespaces = [], since, enabled = true } = options
47
+
48
+ const params = new URLSearchParams()
49
+ // Traffic backend only supports single namespace, use first if provided
50
+ if (namespaces.length === 1) params.set('namespace', namespaces[0])
51
+ if (since) params.set('since', since)
52
+ const queryString = params.toString()
53
+
54
+ return useQuery<TrafficFlowsResponse>({
55
+ queryKey: ['traffic-flows', namespaces, since],
56
+ queryFn: () => fetchJSON(`/traffic/flows${queryString ? `?${queryString}` : ''}`),
57
+ staleTime: 5000, // 5 seconds
58
+ enabled,
59
+ retry: 1,
60
+ })
61
+ }
62
+
63
+ // Get active traffic source
64
+ export function useActiveTrafficSource() {
65
+ return useQuery<{ active: string }>({
66
+ queryKey: ['traffic-source-active'],
67
+ queryFn: () => fetchJSON('/traffic/source'),
68
+ staleTime: 60000, // 1 minute
69
+ })
70
+ }
71
+
72
+ // Set active traffic source
73
+ export function useSetTrafficSource() {
74
+ const queryClient = useQueryClient()
75
+
76
+ return useMutation({
77
+ mutationFn: async (source: string) => {
78
+ const response = await fetch(apiUrl('/traffic/source'), {
79
+ method: 'POST',
80
+ credentials: getCredentialsMode(),
81
+ headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
82
+ body: JSON.stringify({ source }),
83
+ })
84
+ if (!response.ok) {
85
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
86
+ throw new Error(error.error || `HTTP ${response.status}`)
87
+ }
88
+ return response.json()
89
+ },
90
+ meta: {
91
+ errorMessage: 'Failed to change traffic source',
92
+ successMessage: 'Traffic source changed',
93
+ },
94
+ onSuccess: () => {
95
+ queryClient.invalidateQueries({ queryKey: ['traffic-source-active'] })
96
+ queryClient.invalidateQueries({ queryKey: ['traffic-flows'] })
97
+ },
98
+ })
99
+ }
100
+
101
+ // Refetch traffic sources (for polling during wizard)
102
+ export function useRefetchTrafficSources() {
103
+ const queryClient = useQueryClient()
104
+ return () => queryClient.invalidateQueries({ queryKey: ['traffic-sources'] })
105
+ }
106
+
107
+ // Get traffic connection status
108
+ export function useTrafficConnectionStatus() {
109
+ return useQuery<TrafficConnectionInfo>({
110
+ queryKey: ['traffic-connection'],
111
+ queryFn: () => fetchJSON('/traffic/connection'),
112
+ staleTime: 5000, // 5 seconds
113
+ })
114
+ }
115
+
116
+ // Connect to traffic source (starts port-forward if needed)
117
+ export function useTrafficConnect() {
118
+ const queryClient = useQueryClient()
119
+
120
+ return useMutation<TrafficConnectionInfo, Error>({
121
+ mutationFn: async () => {
122
+ const response = await fetch(apiUrl('/traffic/connect'), {
123
+ method: 'POST',
124
+ credentials: getCredentialsMode(),
125
+ headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
126
+ })
127
+ if (!response.ok) {
128
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }))
129
+ throw new Error(error.error || `HTTP ${response.status}`)
130
+ }
131
+ return response.json()
132
+ },
133
+ onSuccess: () => {
134
+ // Invalidate flows to refetch with new connection
135
+ queryClient.invalidateQueries({ queryKey: ['traffic-flows'] })
136
+ queryClient.invalidateQueries({ queryKey: ['traffic-connection'] })
137
+ },
138
+ })
139
+ }
@@ -0,0 +1,272 @@
1
+ import { XCircle, RefreshCw, Loader2, Copy, Check, TerminalSquare } from 'lucide-react'
2
+ import { useState } from 'react'
3
+ import type { ConnectionState } from '../context/ConnectionContext'
4
+ import { ContextSwitcher } from './ContextSwitcher'
5
+ import { parseContextName } from '../utils/context-name'
6
+ import { useOpenLocalTerminal } from '@skyhook-io/k8s-ui'
7
+
8
+ interface ConnectionErrorViewProps {
9
+ connection: ConnectionState
10
+ onRetry: () => void
11
+ isRetrying: boolean
12
+ }
13
+
14
+ interface AuthHints {
15
+ title: string
16
+ hints: string[]
17
+ /** Primary auth command — usually sufficient on its own */
18
+ authCommand?: { label: string; command: string }
19
+ /** Secondary command shown as fallback if primary doesn't resolve the issue */
20
+ fallbackCommand?: { label: string; command: string }
21
+ }
22
+
23
+ function getAuthHints(context: string): AuthHints {
24
+ const parsed = parseContextName(context)
25
+
26
+ switch (parsed.provider) {
27
+ case 'GKE': {
28
+ const result: AuthHints = {
29
+ title: 'GKE Authentication Failed',
30
+ hints: ['Your Google Cloud credentials have expired.'],
31
+ authCommand: { label: 'Re-authenticate with Google Cloud:', command: 'gcloud auth login' },
32
+ }
33
+ if (parsed.region && parsed.account) {
34
+ const isZone = /^[a-z]+-[a-z]+\d+-[a-z]$/.test(parsed.region)
35
+ const flag = isZone ? '--zone' : '--region'
36
+ result.fallbackCommand = {
37
+ label: 'If that doesn\'t work, refresh cluster credentials:',
38
+ command: `gcloud container clusters get-credentials ${parsed.clusterName} ${flag} ${parsed.region} --project ${parsed.account}`,
39
+ }
40
+ }
41
+ return result
42
+ }
43
+ case 'EKS': {
44
+ const result: AuthHints = {
45
+ title: 'EKS Authentication Failed',
46
+ hints: ['Your AWS credentials have expired.'],
47
+ authCommand: { label: 'Re-authenticate with AWS:', command: 'aws sso login' },
48
+ }
49
+ if (parsed.region) {
50
+ result.fallbackCommand = {
51
+ label: 'If that doesn\'t work, refresh cluster credentials:',
52
+ command: `aws eks update-kubeconfig --name ${parsed.clusterName} --region ${parsed.region}`,
53
+ }
54
+ }
55
+ return result
56
+ }
57
+ case 'AKS':
58
+ return {
59
+ title: 'AKS Authentication Failed',
60
+ hints: ['Your Azure credentials have expired.'],
61
+ authCommand: { label: 'Re-authenticate with Azure:', command: 'az login' },
62
+ fallbackCommand: { label: 'If that doesn\'t work, refresh cluster credentials:', command: 'az aks get-credentials --name <cluster> --resource-group <rg>' },
63
+ }
64
+ default:
65
+ return {
66
+ title: 'Authentication Failed',
67
+ hints: [
68
+ 'Your credentials may have expired',
69
+ 'Re-authenticate with your cloud provider and try again',
70
+ ],
71
+ }
72
+ }
73
+ }
74
+
75
+ const errorHints: Record<string, { title: string; hints: string[] }> = {
76
+ config: {
77
+ title: 'No Kubeconfig Found',
78
+ hints: [
79
+ 'Radar could not find a kubeconfig file at ~/.kube/config',
80
+ 'If your kubeconfig is at a custom path, set the KUBECONFIG environment variable in your shell profile (~/.zshrc or ~/.bashrc)',
81
+ 'You can also pass --kubeconfig <path> when launching from the terminal',
82
+ ],
83
+ },
84
+ rbac: {
85
+ title: 'Insufficient Permissions',
86
+ hints: [
87
+ 'Your user account can connect but lacks required RBAC permissions',
88
+ 'Ask your cluster admin for a ClusterRole with list/watch access',
89
+ 'For read-only access, the built-in "view" ClusterRole is usually sufficient',
90
+ 'You can also try: kubectl auth can-i --list',
91
+ ],
92
+ },
93
+ network: {
94
+ title: 'Network Unreachable',
95
+ hints: [
96
+ 'The cluster may be unreachable from your network',
97
+ 'Check if VPN connection is required',
98
+ 'Verify firewall rules allow access',
99
+ 'Confirm the cluster is running',
100
+ ],
101
+ },
102
+ timeout: {
103
+ title: 'Connection Timed Out',
104
+ hints: [
105
+ 'The cluster is taking too long to respond',
106
+ 'The cluster may be under heavy load',
107
+ 'Network latency may be too high',
108
+ 'Try again or check cluster health',
109
+ ],
110
+ },
111
+ unknown: {
112
+ title: 'Connection Failed',
113
+ hints: [
114
+ 'Check your kubeconfig is valid',
115
+ 'Verify the cluster endpoint is correct',
116
+ 'Try switching to a different context',
117
+ ],
118
+ },
119
+ }
120
+
121
+ function CopyableCommand({ command, onRunInTerminal }: { command: string; onRunInTerminal?: (command: string) => void }) {
122
+ const [copied, setCopied] = useState(false)
123
+
124
+ const handleCopy = () => {
125
+ navigator.clipboard.writeText(command).then(() => {
126
+ setCopied(true)
127
+ setTimeout(() => setCopied(false), 2000)
128
+ }).catch(() => {
129
+ // Clipboard API may be unavailable (e.g., non-HTTPS context)
130
+ })
131
+ }
132
+
133
+ return (
134
+ <div className="mt-2 flex items-center gap-2 bg-theme-elevated border border-theme-border rounded-md px-3 py-2 group">
135
+ <code className="text-xs font-mono text-theme-text-primary flex-1 select-all break-all">
136
+ {command}
137
+ </code>
138
+ {onRunInTerminal && (
139
+ <button
140
+ onClick={() => onRunInTerminal(command)}
141
+ className="shrink-0 text-theme-text-tertiary hover:text-theme-text-secondary transition-colors"
142
+ title="Run in terminal"
143
+ >
144
+ <TerminalSquare className="w-3.5 h-3.5" />
145
+ </button>
146
+ )}
147
+ <button
148
+ onClick={handleCopy}
149
+ className="shrink-0 text-theme-text-tertiary hover:text-theme-text-secondary transition-colors"
150
+ title="Copy to clipboard"
151
+ >
152
+ {copied ? (
153
+ <Check className="w-3.5 h-3.5 text-green-400" />
154
+ ) : (
155
+ <Copy className="w-3.5 h-3.5" />
156
+ )}
157
+ </button>
158
+ </div>
159
+ )
160
+ }
161
+
162
+ export function ConnectionErrorView({ connection, onRetry, isRetrying }: ConnectionErrorViewProps) {
163
+ // For auth errors, generate context-aware hints with a specific re-auth command
164
+ const isAuth = connection.errorType === 'auth'
165
+ const authInfo = isAuth ? getAuthHints(connection.context || '') : null
166
+ const errorInfo = authInfo || errorHints[connection.errorType || 'unknown'] || errorHints.unknown
167
+ const openLocalTerminal = useOpenLocalTerminal()
168
+
169
+ // Build a command that auto-retries connection after successful auth
170
+ const retryCmd = `curl -s -X POST http://${window.location.host}/api/connection/retry > /dev/null`
171
+
172
+ const handleAuthInTerminal = () => {
173
+ if (!authInfo?.authCommand) return
174
+ openLocalTerminal({
175
+ initialCommand: `${authInfo.authCommand.command} && ${retryCmd}`,
176
+ title: 'Auth',
177
+ })
178
+ }
179
+
180
+ const handleRunInTerminal = (command: string) => {
181
+ openLocalTerminal({ initialCommand: command, title: 'Auth' })
182
+ }
183
+
184
+ return (
185
+ <div className="flex-1 flex items-start justify-center pt-16 px-8">
186
+ <div className="max-w-lg w-full">
187
+ <div className="flex flex-col items-center text-center">
188
+ <div className="w-16 h-16 rounded-full bg-red-500/10 flex items-center justify-center mb-6">
189
+ <XCircle className="w-10 h-10 text-red-400" />
190
+ </div>
191
+
192
+ <h2 className="text-xl font-semibold text-theme-text-primary mb-2">
193
+ {connection.errorType === 'config' ? 'No Cluster Configuration' : 'Cannot Connect to Cluster'}
194
+ </h2>
195
+
196
+ <p className="text-sm text-theme-text-secondary mb-1">
197
+ Context: <span className="font-mono text-theme-text-primary">{connection.context || '(none)'}</span>
198
+ </p>
199
+
200
+ {connection.clusterName && (
201
+ <p className="text-sm text-theme-text-secondary mb-4">
202
+ Cluster: <span className="font-mono text-theme-text-primary">{connection.clusterName}</span>
203
+ </p>
204
+ )}
205
+
206
+ <div className="w-full bg-theme-surface border border-theme-border rounded-lg p-4 mb-6 text-left">
207
+ <h3 className="text-sm font-medium text-theme-text-primary mb-2">
208
+ {errorInfo.title}
209
+ </h3>
210
+ <ul className="text-sm text-theme-text-secondary space-y-1">
211
+ {errorInfo.hints.map((hint, i) => (
212
+ <li key={i} className="flex items-start gap-2">
213
+ <span className="text-theme-text-tertiary mt-0.5">-</span>
214
+ <span>{hint}</span>
215
+ </li>
216
+ ))}
217
+ </ul>
218
+ {authInfo?.authCommand && (
219
+ <div className="mt-3">
220
+ <p className="text-xs text-theme-text-tertiary">{authInfo.authCommand.label}</p>
221
+ <CopyableCommand command={authInfo.authCommand.command} onRunInTerminal={handleRunInTerminal} />
222
+ <button
223
+ onClick={handleAuthInTerminal}
224
+ className="mt-3 w-full inline-flex items-center justify-center gap-2 px-3 py-2 text-xs font-medium btn-brand rounded-md"
225
+ >
226
+ <TerminalSquare className="w-3.5 h-3.5" />
227
+ Authenticate in terminal
228
+ </button>
229
+ </div>
230
+ )}
231
+ {authInfo?.fallbackCommand && (
232
+ <div className="mt-4 pt-3 border-t border-theme-border/50">
233
+ <p className="text-xs text-theme-text-tertiary">{authInfo.fallbackCommand.label}</p>
234
+ <CopyableCommand command={authInfo.fallbackCommand.command} onRunInTerminal={handleRunInTerminal} />
235
+ </div>
236
+ )}
237
+ </div>
238
+
239
+ {connection.error && (
240
+ <div className="w-full bg-theme-elevated border border-theme-border rounded-lg p-3 mb-6 overflow-auto max-h-32">
241
+ <code className="text-xs text-red-400 font-mono whitespace-pre-wrap break-all">
242
+ {connection.error}
243
+ </code>
244
+ </div>
245
+ )}
246
+
247
+ <div className="flex items-center gap-5">
248
+ <button
249
+ onClick={onRetry}
250
+ disabled={isRetrying}
251
+ className="inline-flex items-center gap-2 px-4 py-2 btn-brand rounded-lg"
252
+ >
253
+ {isRetrying ? (
254
+ <>
255
+ <Loader2 className="w-4 h-4 animate-spin" />
256
+ Connecting...
257
+ </>
258
+ ) : (
259
+ <>
260
+ <RefreshCw className="w-4 h-4" />
261
+ Retry Connection
262
+ </>
263
+ )}
264
+ </button>
265
+
266
+ {connection.errorType !== 'config' && <ContextSwitcher />}
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ )
272
+ }