@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,63 @@
1
+ import { ShieldCheck, ShieldAlert } from 'lucide-react'
2
+ import type { ReactNode } from 'react'
3
+ import { useArgoRevisionMetadata } from '../../api/client'
4
+ import { Tooltip } from '../ui/Tooltip'
5
+
6
+ // Argo returns the author as "Name <email>"; show just the name.
7
+ function authorName(author?: string): string {
8
+ if (!author) return ''
9
+ const lt = author.indexOf('<')
10
+ return (lt >= 0 ? author.slice(0, lt) : author).trim()
11
+ }
12
+
13
+ // signatureInfo is a raw GPG verification line. "Good signature" → verified;
14
+ // any other non-empty value → a check ran but didn't verify; empty → unsigned.
15
+ function signatureState(sig?: string): 'good' | 'bad' | null {
16
+ if (!sig) return null
17
+ return /good signature/i.test(sig) ? 'good' : 'bad'
18
+ }
19
+
20
+ // RevisionMetaChip hydrates the Argo status-strip revision with Git commit
21
+ // detail (author, subject, signature) fetched from the Argo CD API. Renders
22
+ // nothing until data arrives, so the bare SHA is never blocked; the whole thing
23
+ // is gated by the host on capabilities.revisionMetadataAvailable.
24
+ export function RevisionMetaChip({
25
+ appNamespace,
26
+ appName,
27
+ revision,
28
+ }: {
29
+ appNamespace: string
30
+ appName: string
31
+ revision: string
32
+ }): ReactNode {
33
+ // The host only renders this chip when revision metadata is available, so no
34
+ // extra enabled gate is needed — the hook's own appName/revision guard suffices.
35
+ const { data } = useArgoRevisionMetadata(appNamespace, appName, revision)
36
+ if (!data) return null
37
+
38
+ const author = authorName(data.author)
39
+ const sig = signatureState(data.signatureInfo)
40
+ const subject = data.message?.split('\n')[0]?.trim()
41
+ if (!author && !sig && !subject) return null
42
+
43
+ return (
44
+ <>
45
+ {author && <span className="shrink-0 text-theme-text-secondary">· {author}</span>}
46
+ {subject && (
47
+ <Tooltip content={data.message ?? subject} delay={300} wrapperClassName="min-w-0">
48
+ <span className="max-w-[32ch] truncate text-theme-text-tertiary">“{subject}”</span>
49
+ </Tooltip>
50
+ )}
51
+ {sig === 'good' && (
52
+ <Tooltip content={data.signatureInfo ?? 'Signed commit'} delay={300} wrapperClassName="inline-flex shrink-0">
53
+ <ShieldCheck className="h-3 w-3 text-green-600 dark:text-green-400/80" />
54
+ </Tooltip>
55
+ )}
56
+ {sig === 'bad' && (
57
+ <Tooltip content={data.signatureInfo ?? 'Signature not verified'} delay={300} wrapperClassName="inline-flex shrink-0">
58
+ <ShieldAlert className="h-3 w-3 text-amber-600 dark:text-amber-400/80" />
59
+ </Tooltip>
60
+ )}
61
+ </>
62
+ )
63
+ }
@@ -1,13 +1,14 @@
1
1
  import { useState, useMemo } from 'react'
2
2
  import { Search, RefreshCw, Package, Database, AlertCircle, ExternalLink, ChevronDown, Star, Shield, BadgeCheck, Building2, Globe, ArrowUpDown, FileJson, PenTool } from 'lucide-react'
3
- import { PaneLoader } from '@skyhook-io/k8s-ui'
3
+ import { PaneLoader, Input } from '@skyhook-io/k8s-ui'
4
4
  import { clsx } from 'clsx'
5
- import { useHelmRepositories, useSearchCharts, useUpdateRepository, useArtifactHubSearch, type ArtifactHubSortOption } from '../../api/client'
5
+ import { useHelmRepositories, useSearchCharts, useUpdateRepository, useUpdateRepositorySilent, useArtifactHubSearch, type ArtifactHubSortOption } from '../../api/client'
6
6
  import { useCanHelmWrite } from '../../contexts/CapabilitiesContext'
7
7
  import type { ChartInfo, HelmRepository, ArtifactHubChart, ChartSource } from '../../types'
8
8
  import { formatAge } from './helm-utils'
9
9
  import { SEVERITY_BADGE } from '../../utils/badge-colors'
10
10
  import { Tooltip } from '../ui/Tooltip'
11
+ import { useToast } from '../ui/Toast'
11
12
 
12
13
  interface ChartBrowserProps {
13
14
  onChartSelect: (repo: string, chart: string, version: string, source: ChartSource) => void
@@ -26,7 +27,7 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
26
27
 
27
28
  // Repo refresh is gated only by `requireHelmWrite` on the backend
28
29
  // (handleUpdateRepository deliberately skips requireCloudRole — it
29
- // mutates pod-local chart cache, not cluster state). So the SPA gate
30
+ // mutates pod-local chart cache, not cluster state). So the frontend gate
30
31
  // here must NOT include the Cloud role check, or Cloud viewers with
31
32
  // rbac.helm=true would be blocked from a refresh the backend allows.
32
33
  const canHelmWrite = useCanHelmWrite()
@@ -66,21 +67,54 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
66
67
  return groups
67
68
  }, [filteredLocalCharts])
68
69
 
70
+ // Silent variant for the bulk path so the global MutationCache
71
+ // doesn't fire a per-call "Failed to update repository" toast
72
+ // — handleUpdateAllRepos surfaces a single aggregate toast
73
+ // that names the failed repos.
74
+ const updateRepoSilentMutation = useUpdateRepositorySilent()
75
+ const { showError, showSuccess } = useToast()
76
+ // updateRepoSilentMutation.isPending flips to false BETWEEN
77
+ // sequential mutateAsync calls, briefly re-enabling the bulk
78
+ // button mid-loop. Track the whole-batch state explicitly so
79
+ // the user can't kick off a second concurrent batch.
80
+ const [isBatchUpdating, setIsBatchUpdating] = useState(false)
81
+
69
82
  const handleUpdateRepo = async (repoName: string) => {
70
83
  await updateRepoMutation.mutateAsync(repoName)
71
84
  refetchCharts()
72
85
  }
73
86
 
74
87
  const handleUpdateAllRepos = async () => {
75
- if (!repositories) return
76
- for (const repo of repositories) {
77
- try {
78
- await updateRepoMutation.mutateAsync(repo.name)
79
- } catch {
80
- // Continue with other repos
88
+ if (!repositories || repositories.length === 0 || isBatchUpdating) return
89
+ setIsBatchUpdating(true)
90
+ const failed: string[] = []
91
+ try {
92
+ for (const repo of repositories) {
93
+ try {
94
+ await updateRepoSilentMutation.mutateAsync(repo.name)
95
+ } catch (err) {
96
+ failed.push(repo.name)
97
+ console.warn(`helm repo update failed for "${repo.name}":`, err)
98
+ }
81
99
  }
100
+ } finally {
101
+ setIsBatchUpdating(false)
82
102
  }
83
103
  refetchCharts()
104
+ const ok = repositories.length - failed.length
105
+ if (failed.length === 0) {
106
+ showSuccess(`Updated ${ok} ${ok === 1 ? 'repository' : 'repositories'}`)
107
+ } else if (ok === 0) {
108
+ showError(
109
+ `Failed to update ${failed.length} ${failed.length === 1 ? 'repository' : 'repositories'}`,
110
+ `Failed: ${failed.join(', ')}`,
111
+ )
112
+ } else {
113
+ showError(
114
+ `Updated ${ok}/${repositories.length} repositories`,
115
+ `Failed: ${failed.join(', ')}`,
116
+ )
117
+ }
84
118
  }
85
119
 
86
120
  const isLoading = chartSource === 'local' ? chartsLoading : artifactHubLoading
@@ -148,7 +182,7 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
148
182
  onClick={() => { setSelectedRepo('all'); setRepoDropdownOpen(false) }}
149
183
  className={clsx(
150
184
  'w-full px-3 py-2 text-left text-sm hover:bg-theme-hover flex items-center justify-between',
151
- selectedRepo === 'all' ? 'text-blue-400' : 'text-theme-text-primary'
185
+ selectedRepo === 'all' ? 'text-accent' : 'text-theme-text-primary'
152
186
  )}
153
187
  >
154
188
  <span>All Repositories</span>
@@ -156,7 +190,7 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
156
190
  </button>
157
191
  <div className="border-t border-theme-border my-1" />
158
192
  {reposLoading ? (
159
- <div className="px-3 py-2 text-sm text-theme-text-tertiary">Loading...</div>
193
+ <div className="px-3 py-2 text-sm text-theme-text-tertiary">Loading…</div>
160
194
  ) : repositories?.length === 0 ? (
161
195
  <div className="px-3 py-2 text-sm text-theme-text-tertiary">No repositories configured</div>
162
196
  ) : (
@@ -186,9 +220,9 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
186
220
  type="checkbox"
187
221
  checked={showOfficialOnly}
188
222
  onChange={(e) => setShowOfficialOnly(e.target.checked)}
189
- className="rounded border-theme-border text-blue-500 focus:ring-blue-500"
223
+ className="rounded border-theme-border text-accent focus:ring-accent"
190
224
  />
191
- <BadgeCheck className="w-3.5 h-3.5 text-blue-400" />
225
+ <BadgeCheck className="w-3.5 h-3.5 text-accent" />
192
226
  Official
193
227
  </label>
194
228
  <label className="flex items-center gap-1.5 text-sm text-theme-text-secondary">
@@ -196,7 +230,7 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
196
230
  type="checkbox"
197
231
  checked={showVerifiedOnly}
198
232
  onChange={(e) => setShowVerifiedOnly(e.target.checked)}
199
- className="rounded border-theme-border text-blue-500 focus:ring-blue-500"
233
+ className="rounded border-theme-border text-accent focus:ring-accent"
200
234
  />
201
235
  <Shield className="w-3.5 h-3.5 text-green-400" />
202
236
  Verified
@@ -207,7 +241,7 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
207
241
  <select
208
242
  value={artifactHubSort}
209
243
  onChange={(e) => setArtifactHubSort(e.target.value as ArtifactHubSortOption)}
210
- className="bg-theme-elevated border border-theme-border-light rounded px-2 py-1 text-sm text-theme-text-primary focus:outline-none focus:ring-2 focus:ring-blue-500"
244
+ className="bg-theme-elevated border border-theme-border-light rounded px-2 py-1 text-sm text-theme-text-primary focus:outline-none focus:ring-2 focus:ring-accent"
211
245
  >
212
246
  <option value="relevance">Relevance</option>
213
247
  <option value="stars">Stars</option>
@@ -220,12 +254,11 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
220
254
  {/* Search */}
221
255
  <div className="flex-1 relative">
222
256
  <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-theme-text-tertiary" />
223
- <input
224
- type="text"
257
+ <Input
225
258
  placeholder={chartSource === 'local' ? "Search charts..." : "Search ArtifactHub..."}
226
259
  value={searchTerm}
227
260
  onChange={(e) => setSearchTerm(e.target.value)}
228
- className="w-full max-w-md pl-10 pr-4 py-2 bg-theme-elevated border border-theme-border-light rounded-lg text-sm text-theme-text-primary placeholder-theme-text-disabled focus:outline-none focus:ring-2 focus:ring-blue-500"
261
+ className="w-full max-w-md pl-10 pr-4 py-2 bg-theme-elevated border border-theme-border-light rounded-lg text-sm text-theme-text-primary placeholder-theme-text-disabled focus:outline-none focus:ring-2 focus:ring-accent"
229
262
  />
230
263
  </div>
231
264
 
@@ -237,7 +270,7 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
237
270
  type="checkbox"
238
271
  checked={showAllVersions}
239
272
  onChange={(e) => setShowAllVersions(e.target.checked)}
240
- className="rounded border-theme-border text-blue-500 focus:ring-blue-500"
273
+ className="rounded border-theme-border text-accent focus:ring-accent"
241
274
  />
242
275
  All versions
243
276
  </label>
@@ -246,10 +279,10 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
246
279
  <Tooltip content={canHelmWrite ? "Update all repositories" : helmWriteReason}>
247
280
  <button
248
281
  onClick={handleUpdateAllRepos}
249
- disabled={updateRepoMutation.isPending || !canHelmWrite}
282
+ disabled={isBatchUpdating || !canHelmWrite}
250
283
  className="p-2 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded-lg disabled:opacity-50"
251
284
  >
252
- <RefreshCw className={clsx('w-4 h-4', updateRepoMutation.isPending && 'animate-spin')} />
285
+ <RefreshCw className={clsx('w-4 h-4', isBatchUpdating && 'animate-spin')} />
253
286
  </button>
254
287
  </Tooltip>
255
288
  </>
@@ -273,14 +306,32 @@ export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
273
306
  <div className="text-sm mt-1">
274
307
  <p>No Helm repositories configured.</p>
275
308
  <p className="mt-1">
276
- Add repositories using <code className="bg-theme-elevated px-1 rounded">helm repo add</code>
309
+ Add repositories using <code className="inline-code">helm repo add</code>
277
310
  </p>
278
311
  <p className="mt-2">
279
- Or try searching on <button onClick={() => setChartSource('artifacthub')} className="text-blue-400 hover:underline">ArtifactHub</button>
312
+ Or try searching on <button onClick={() => setChartSource('artifacthub')} className="text-accent-text hover:underline">ArtifactHub</button>
280
313
  </p>
281
314
  </div>
282
315
  ) : (
283
- <p className="text-sm mt-1">Try updating your repositories</p>
316
+ <div className="text-sm mt-1 flex flex-col items-center gap-2">
317
+ <p>Your repositories may be out of date.</p>
318
+ <Tooltip content={canHelmWrite ? 'Run helm repo update on every configured repository' : helmWriteReason}>
319
+ <button
320
+ onClick={handleUpdateAllRepos}
321
+ disabled={isBatchUpdating || !canHelmWrite}
322
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs btn-brand rounded disabled:opacity-50 disabled:pointer-events-none"
323
+ >
324
+ <RefreshCw className={`w-3.5 h-3.5 ${isBatchUpdating ? 'animate-spin' : ''}`} />
325
+ {isBatchUpdating ? 'Updating…' : 'Update all repositories'}
326
+ </button>
327
+ </Tooltip>
328
+ <button
329
+ onClick={() => setChartSource('artifacthub')}
330
+ className="text-xs text-blue-400 hover:underline"
331
+ >
332
+ Or browse ArtifactHub instead
333
+ </button>
334
+ </div>
284
335
  )}
285
336
  </div>
286
337
  </div>
@@ -375,7 +426,7 @@ function RepoDropdownItem({ repo, isSelected, onSelect, onUpdate, isUpdating, ca
375
426
  onClick={onSelect}
376
427
  className={clsx(
377
428
  'flex-1 text-left text-sm truncate',
378
- isSelected ? 'text-blue-400' : 'text-theme-text-primary'
429
+ isSelected ? 'text-accent' : 'text-theme-text-primary'
379
430
  )}
380
431
  >
381
432
  {repo.name}
@@ -385,14 +436,15 @@ function RepoDropdownItem({ repo, isSelected, onSelect, onUpdate, isUpdating, ca
385
436
  </span>
386
437
  )}
387
438
  </button>
439
+ <Tooltip content={canUpdate ? "Update repository" : (cantUpdateReason ?? "Helm write permissions required")}>
388
440
  <button
389
441
  onClick={(e) => { e.stopPropagation(); onUpdate() }}
390
442
  disabled={isUpdating || !canUpdate}
391
- className="p-1 text-theme-text-tertiary hover:text-theme-text-primary opacity-0 group-hover:opacity-100 transition-opacity disabled:opacity-50"
392
- title={canUpdate ? "Update repository" : (cantUpdateReason ?? "Helm write permissions required")}
443
+ className="p-1 text-theme-text-tertiary hover:text-theme-text-primary opacity-0 group-hover:opacity-100 transition-opacity disabled:opacity-50 disabled:pointer-events-none"
393
444
  >
394
445
  <RefreshCw className={clsx('w-3.5 h-3.5', isUpdating && 'animate-spin')} />
395
446
  </button>
447
+ </Tooltip>
396
448
  </div>
397
449
  )
398
450
  }
@@ -413,7 +465,7 @@ function LocalChartCard({ chart, onSelect }: LocalChartCardProps) {
413
465
  <img
414
466
  src={chart.icon}
415
467
  alt=""
416
- className="w-10 h-10 rounded object-contain bg-white/10 p-1"
468
+ className="w-10 h-10 rounded object-contain bg-theme-elevated p-1"
417
469
  onError={(e) => {
418
470
  (e.target as HTMLImageElement).style.display = 'none'
419
471
  }}
@@ -425,7 +477,9 @@ function LocalChartCard({ chart, onSelect }: LocalChartCardProps) {
425
477
  )}
426
478
  <div className="flex-1 min-w-0">
427
479
  <div className="flex items-center gap-2">
428
- <h4 className="text-sm font-medium text-theme-text-primary truncate">{chart.name}</h4>
480
+ <Tooltip content={chart.name} wrapperClassName="min-w-0 flex-1">
481
+ <h4 className="text-sm font-medium text-theme-text-primary truncate">{chart.name}</h4>
482
+ </Tooltip>
429
483
  {chart.deprecated && (
430
484
  <span className={clsx('px-1 py-0.5 text-[10px] rounded', SEVERITY_BADGE.warning)}>
431
485
  deprecated
@@ -476,7 +530,7 @@ function ArtifactHubChartCard({ chart, onSelect }: ArtifactHubChartCardProps) {
476
530
  <img
477
531
  src={chart.logoUrl}
478
532
  alt=""
479
- className="w-12 h-12 rounded object-contain bg-white/10 p-1 shrink-0"
533
+ className="w-12 h-12 rounded object-contain bg-theme-elevated p-1 shrink-0"
480
534
  onError={(e) => {
481
535
  (e.target as HTMLImageElement).style.display = 'none'
482
536
  }}
@@ -489,7 +543,9 @@ function ArtifactHubChartCard({ chart, onSelect }: ArtifactHubChartCardProps) {
489
543
 
490
544
  {/* Name and org */}
491
545
  <div className="flex-1 min-w-0">
492
- <h4 className="text-sm font-medium text-theme-text-primary truncate">{chart.name}</h4>
546
+ <Tooltip content={chart.name} wrapperClassName="w-full">
547
+ <h4 className="text-sm font-medium text-theme-text-primary truncate">{chart.name}</h4>
548
+ </Tooltip>
493
549
  <div className="flex items-center gap-2 mt-0.5 text-xs text-theme-text-tertiary">
494
550
  <span className="flex items-center gap-1">
495
551
  <Building2 className="w-3 h-3" />