@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.
- package/README.md +67 -0
- package/package.json +80 -0
- package/src/App.tsx +1538 -0
- package/src/RadarApp.tsx +145 -0
- package/src/api/apiResources.ts +28 -0
- package/src/api/client.ts +2583 -0
- package/src/api/config.ts +116 -0
- package/src/api/traffic.ts +139 -0
- package/src/components/ConnectionErrorView.tsx +272 -0
- package/src/components/ContextSwitcher.tsx +481 -0
- package/src/components/DebugOverlay.tsx +94 -0
- package/src/components/UserMenu.tsx +87 -0
- package/src/components/audit/AuditSettingsDialog.tsx +162 -0
- package/src/components/audit/AuditView.tsx +123 -0
- package/src/components/cost/CostTrendChart.tsx +388 -0
- package/src/components/cost/CostView.tsx +545 -0
- package/src/components/dock/BottomDock.tsx +96 -0
- package/src/components/dock/DockContext.tsx +11 -0
- package/src/components/dock/LocalTerminalTab.tsx +22 -0
- package/src/components/dock/LogsTab.tsx +26 -0
- package/src/components/dock/NodeTerminalTab.tsx +50 -0
- package/src/components/dock/TerminalTab.tsx +42 -0
- package/src/components/dock/TrafficFlowListTab.tsx +18 -0
- package/src/components/dock/WorkloadLogsTab.tsx +23 -0
- package/src/components/dock/index.ts +2 -0
- package/src/components/gitops/GitOpsActions.tsx +1 -0
- package/src/components/gitops/GitOpsStatusBadge.tsx +1 -0
- package/src/components/gitops/ManagedResourcesList.tsx +1 -0
- package/src/components/gitops/SyncCountdown.tsx +1 -0
- package/src/components/gitops/index.ts +4 -0
- package/src/components/helm/ChartBrowser.tsx +580 -0
- package/src/components/helm/HelmReleaseDrawer.tsx +774 -0
- package/src/components/helm/HelmView.tsx +475 -0
- package/src/components/helm/InstallWizard.tsx +1060 -0
- package/src/components/helm/ManifestDiffViewer.tsx +91 -0
- package/src/components/helm/ManifestViewer.tsx +61 -0
- package/src/components/helm/OwnedResources.tsx +465 -0
- package/src/components/helm/RevisionHistory.tsx +167 -0
- package/src/components/helm/ValuesDiffPreview.tsx +190 -0
- package/src/components/helm/ValuesViewer.tsx +365 -0
- package/src/components/helm/helm-utils.ts +37 -0
- package/src/components/home/ActivitySummary.tsx +262 -0
- package/src/components/home/CertificateHealthCard.tsx +105 -0
- package/src/components/home/ClusterHealthCard.tsx +483 -0
- package/src/components/home/CostCard.tsx +112 -0
- package/src/components/home/HealthRing.tsx +1 -0
- package/src/components/home/HelmSummary.tsx +129 -0
- package/src/components/home/HomeView.tsx +224 -0
- package/src/components/home/MCPSetupDialog.tsx +417 -0
- package/src/components/home/NetworkPolicyCoverageCard.tsx +109 -0
- package/src/components/home/TopologyPreview.tsx +219 -0
- package/src/components/home/TrafficSummary.tsx +154 -0
- package/src/components/logs/JsonLogLine.tsx +1 -0
- package/src/components/logs/LogCore.tsx +2 -0
- package/src/components/logs/LogsViewer.tsx +44 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +40 -0
- package/src/components/logs/useLogBuffer.ts +2 -0
- package/src/components/logs/useLogSearch.ts +1 -0
- package/src/components/portforward/PortForwardButton.tsx +375 -0
- package/src/components/portforward/PortForwardManager.tsx +871 -0
- package/src/components/resource/PrometheusCharts.tsx +687 -0
- package/src/components/resource-drawer/ResourceDrawer.tsx +214 -0
- package/src/components/resources/ImageFilesystemModal.tsx +745 -0
- package/src/components/resources/PodFilesystemModal.tsx +407 -0
- package/src/components/resources/ResourceDetailDrawer.tsx +43 -0
- package/src/components/resources/ResourcesView.tsx +190 -0
- package/src/components/resources/drawer-components.tsx +1 -0
- package/src/components/resources/file-browser-utils.ts +35 -0
- package/src/components/resources/renderers/AlertRenderer.tsx +1 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +17 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +1 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +1 -0
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +1 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +1 -0
- package/src/components/resources/renderers/CertificateRenderer.tsx +1 -0
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +1 -0
- package/src/components/resources/renderers/ChallengeRenderer.tsx +1 -0
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +1 -0
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +1 -0
- package/src/components/resources/renderers/CronJobRenderer.tsx +1 -0
- package/src/components/resources/renderers/EventRenderer.tsx +1 -0
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +1 -0
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/GatewayRenderer.tsx +1 -0
- package/src/components/resources/renderers/GenericRenderer.tsx +1 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +1 -0
- package/src/components/resources/renderers/HPARenderer.tsx +1 -0
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +1 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +1 -0
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +1 -0
- package/src/components/resources/renderers/JobRenderer.tsx +1 -0
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +1 -0
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +1 -0
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +1 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +1 -0
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +1 -0
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +1 -0
- package/src/components/resources/renderers/KustomizationRenderer.tsx +1 -0
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/LeaseRenderer.tsx +1 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +1 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +44 -0
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +1 -0
- package/src/components/resources/renderers/OrderRenderer.tsx +1 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +1 -0
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +1 -0
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +1 -0
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +1 -0
- package/src/components/resources/renderers/PodRenderer.tsx +94 -0
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +1 -0
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +1 -0
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +1 -0
- package/src/components/resources/renderers/RoleRenderer.tsx +1 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +1 -0
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/SbomReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +1 -0
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +1 -0
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +1 -0
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +1 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +26 -0
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/StorageClassRenderer.tsx +1 -0
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +1 -0
- package/src/components/resources/renderers/VPARenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +1 -0
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +1 -0
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +1 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +1 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +1 -0
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +1 -0
- package/src/components/resources/renderers/WorkloadRenderer.tsx +52 -0
- package/src/components/resources/renderers/argo-cells.tsx +1 -0
- package/src/components/resources/renderers/certmanager-cells.tsx +1 -0
- package/src/components/resources/renderers/cnpg-cells.tsx +1 -0
- package/src/components/resources/renderers/eso-cells.tsx +1 -0
- package/src/components/resources/renderers/flux-cells.tsx +1 -0
- package/src/components/resources/renderers/index.ts +91 -0
- package/src/components/resources/renderers/istio-cells.tsx +1 -0
- package/src/components/resources/renderers/karpenter-cells.tsx +1 -0
- package/src/components/resources/renderers/keda-cells.tsx +1 -0
- package/src/components/resources/renderers/knative-cells.tsx +1 -0
- package/src/components/resources/renderers/kyverno-cells.tsx +1 -0
- package/src/components/resources/renderers/prometheus-cells.tsx +1 -0
- package/src/components/resources/renderers/traefik-cells.tsx +1 -0
- package/src/components/resources/renderers/trivy-cells.tsx +1 -0
- package/src/components/resources/renderers/trivy-shared.tsx +1 -0
- package/src/components/resources/renderers/velero-cells.tsx +1 -0
- package/src/components/resources/resource-utils-argo.ts +2 -0
- package/src/components/resources/resource-utils-certmanager.ts +2 -0
- package/src/components/resources/resource-utils-cnpg.ts +2 -0
- package/src/components/resources/resource-utils-eso.ts +2 -0
- package/src/components/resources/resource-utils-flux.ts +2 -0
- package/src/components/resources/resource-utils-istio.ts +2 -0
- package/src/components/resources/resource-utils-karpenter.ts +2 -0
- package/src/components/resources/resource-utils-keda.ts +2 -0
- package/src/components/resources/resource-utils-knative.ts +2 -0
- package/src/components/resources/resource-utils-kyverno.ts +2 -0
- package/src/components/resources/resource-utils-prometheus.ts +2 -0
- package/src/components/resources/resource-utils-traefik.ts +1 -0
- package/src/components/resources/resource-utils-trivy.ts +2 -0
- package/src/components/resources/resource-utils-velero.ts +2 -0
- package/src/components/resources/resource-utils.ts +5 -0
- package/src/components/settings/SettingsDialog.tsx +537 -0
- package/src/components/shared/CreateResourceDialog.tsx +17 -0
- package/src/components/shared/EditableYamlView.tsx +24 -0
- package/src/components/shared/LargeClusterNamespacePicker.tsx +70 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +31 -0
- package/src/components/timeline/DiffViewer.tsx +1 -0
- package/src/components/timeline/TimelineList.tsx +69 -0
- package/src/components/timeline/TimelineSwimlanes.tsx +1308 -0
- package/src/components/timeline/TimelineView.tsx +157 -0
- package/src/components/timeline/shared.tsx +1 -0
- package/src/components/traffic/TrafficFilterSidebar.tsx +571 -0
- package/src/components/traffic/TrafficFlowList.tsx +415 -0
- package/src/components/traffic/TrafficFlowListContext.tsx +68 -0
- package/src/components/traffic/TrafficGraph.tsx +1546 -0
- package/src/components/traffic/TrafficView.tsx +1213 -0
- package/src/components/traffic/TrafficWizard.tsx +386 -0
- package/src/components/traffic/index.ts +3 -0
- package/src/components/ui/CodeViewer.tsx +8 -0
- package/src/components/ui/CommandPalette.tsx +460 -0
- package/src/components/ui/ConfirmDialog.tsx +1 -0
- package/src/components/ui/DiagnosticsOverlay.tsx +619 -0
- package/src/components/ui/ErrorBoundary.tsx +46 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +1 -0
- package/src/components/ui/Markdown.tsx +108 -0
- package/src/components/ui/MetricsChart.tsx +1 -0
- package/src/components/ui/NamespaceSelector.tsx +436 -0
- package/src/components/ui/ResourceBar.tsx +1 -0
- package/src/components/ui/ShortcutHelpOverlay.tsx +301 -0
- package/src/components/ui/Toast.tsx +1 -0
- package/src/components/ui/Tooltip.tsx +1 -0
- package/src/components/ui/UpdateNotification.tsx +299 -0
- package/src/components/ui/YamlEditor.tsx +1 -0
- package/src/components/workload/WorkloadView.tsx +532 -0
- package/src/context/ConnectionContext.tsx +173 -0
- package/src/context/ContextSwitchContext.tsx +56 -0
- package/src/context/NavCustomization.tsx +62 -0
- package/src/context/ThemeContext.tsx +97 -0
- package/src/contexts/CapabilitiesContext.tsx +130 -0
- package/src/hooks/useAnimatedUnmount.ts +1 -0
- package/src/hooks/useDesktopDownload.ts +41 -0
- package/src/hooks/useEventSource.ts +262 -0
- package/src/hooks/useFavorites.ts +69 -0
- package/src/hooks/useKeyboardShortcuts.tsx +7 -0
- package/src/hooks/useRefreshAnimation.ts +1 -0
- package/src/index.css +243 -0
- package/src/index.ts +17 -0
- package/src/main.tsx +158 -0
- package/src/types/gitops.ts +2 -0
- package/src/types.ts +3 -0
- package/src/utils/animation.ts +2 -0
- package/src/utils/badge-colors.ts +2 -0
- package/src/utils/context-name.ts +2 -0
- package/src/utils/desktop-download.ts +66 -0
- package/src/utils/desktop-open-folder.ts +21 -0
- package/src/utils/format.ts +2 -0
- package/src/utils/log-format.ts +12 -0
- package/src/utils/navigation.ts +23 -0
- package/src/utils/resource-hierarchy.ts +2 -0
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/skeleton-yaml.ts +2 -0
- package/src/utils/traffic-colors.ts +54 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
import { useState, useMemo } from 'react'
|
|
2
|
+
import { Search, RefreshCw, Package, Database, AlertCircle, ExternalLink, ChevronDown, Star, Shield, BadgeCheck, Building2, Globe, ArrowUpDown, FileJson, PenTool } from 'lucide-react'
|
|
3
|
+
import { clsx } from 'clsx'
|
|
4
|
+
import { useHelmRepositories, useSearchCharts, useUpdateRepository, useArtifactHubSearch, type ArtifactHubSortOption } from '../../api/client'
|
|
5
|
+
import { useCanHelmWrite } from '../../contexts/CapabilitiesContext'
|
|
6
|
+
import type { ChartInfo, HelmRepository, ArtifactHubChart, ChartSource } from '../../types'
|
|
7
|
+
import { formatAge } from './helm-utils'
|
|
8
|
+
import { SEVERITY_BADGE } from '../../utils/badge-colors'
|
|
9
|
+
import { Tooltip } from '../ui/Tooltip'
|
|
10
|
+
|
|
11
|
+
interface ChartBrowserProps {
|
|
12
|
+
onChartSelect: (repo: string, chart: string, version: string, source: ChartSource) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function ChartBrowser({ onChartSelect }: ChartBrowserProps) {
|
|
16
|
+
const [chartSource, setChartSource] = useState<ChartSource>('local')
|
|
17
|
+
const [searchTerm, setSearchTerm] = useState('')
|
|
18
|
+
const [selectedRepo, setSelectedRepo] = useState<string>('all')
|
|
19
|
+
const [showAllVersions, setShowAllVersions] = useState(false)
|
|
20
|
+
const [repoDropdownOpen, setRepoDropdownOpen] = useState(false)
|
|
21
|
+
// ArtifactHub filters
|
|
22
|
+
const [showOfficialOnly, setShowOfficialOnly] = useState(false)
|
|
23
|
+
const [showVerifiedOnly, setShowVerifiedOnly] = useState(false)
|
|
24
|
+
const [artifactHubSort, setArtifactHubSort] = useState<ArtifactHubSortOption>('relevance')
|
|
25
|
+
|
|
26
|
+
const canHelmWrite = useCanHelmWrite()
|
|
27
|
+
|
|
28
|
+
// Local repo hooks
|
|
29
|
+
const { data: repositories, isLoading: reposLoading } = useHelmRepositories()
|
|
30
|
+
const { data: searchResult, isLoading: chartsLoading, refetch: refetchCharts } = useSearchCharts(
|
|
31
|
+
searchTerm,
|
|
32
|
+
showAllVersions,
|
|
33
|
+
chartSource === 'local'
|
|
34
|
+
)
|
|
35
|
+
const updateRepoMutation = useUpdateRepository()
|
|
36
|
+
|
|
37
|
+
// ArtifactHub hook - only search when there's a search term
|
|
38
|
+
const { data: artifactHubResult, isLoading: artifactHubLoading } = useArtifactHubSearch(
|
|
39
|
+
searchTerm,
|
|
40
|
+
{ official: showOfficialOnly, verified: showVerifiedOnly, limit: 60, sort: artifactHubSort },
|
|
41
|
+
chartSource === 'artifacthub' && searchTerm.length > 0
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
// Filter local charts by selected repository
|
|
45
|
+
const filteredLocalCharts = useMemo(() => {
|
|
46
|
+
if (!searchResult?.charts) return []
|
|
47
|
+
if (selectedRepo === 'all') return searchResult.charts
|
|
48
|
+
return searchResult.charts.filter(c => c.repository === selectedRepo)
|
|
49
|
+
}, [searchResult?.charts, selectedRepo])
|
|
50
|
+
|
|
51
|
+
// Group local charts by repository for display
|
|
52
|
+
const chartsByRepo = useMemo(() => {
|
|
53
|
+
const groups = new Map<string, ChartInfo[]>()
|
|
54
|
+
for (const chart of filteredLocalCharts) {
|
|
55
|
+
const existing = groups.get(chart.repository) || []
|
|
56
|
+
existing.push(chart)
|
|
57
|
+
groups.set(chart.repository, existing)
|
|
58
|
+
}
|
|
59
|
+
return groups
|
|
60
|
+
}, [filteredLocalCharts])
|
|
61
|
+
|
|
62
|
+
const handleUpdateRepo = async (repoName: string) => {
|
|
63
|
+
await updateRepoMutation.mutateAsync(repoName)
|
|
64
|
+
refetchCharts()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const handleUpdateAllRepos = async () => {
|
|
68
|
+
if (!repositories) return
|
|
69
|
+
for (const repo of repositories) {
|
|
70
|
+
try {
|
|
71
|
+
await updateRepoMutation.mutateAsync(repo.name)
|
|
72
|
+
} catch {
|
|
73
|
+
// Continue with other repos
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
refetchCharts()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const isLoading = chartSource === 'local' ? chartsLoading : artifactHubLoading
|
|
80
|
+
const totalCount = chartSource === 'local'
|
|
81
|
+
? filteredLocalCharts.length
|
|
82
|
+
: (artifactHubResult?.charts.length ?? 0)
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className="flex flex-col h-full">
|
|
86
|
+
{/* Toolbar */}
|
|
87
|
+
<div className="flex items-center gap-4 px-4 py-3 border-b border-theme-border bg-theme-surface/50 shrink-0">
|
|
88
|
+
<div className="flex items-center gap-2 text-theme-text-secondary">
|
|
89
|
+
<Package className="w-5 h-5" />
|
|
90
|
+
<span className="font-medium">Charts</span>
|
|
91
|
+
{!isLoading && (
|
|
92
|
+
<span className="badge bg-theme-elevated">
|
|
93
|
+
{totalCount}
|
|
94
|
+
</span>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Source toggle */}
|
|
99
|
+
<div className="flex items-center bg-theme-elevated rounded-lg p-0.5 border border-theme-border-light">
|
|
100
|
+
<button
|
|
101
|
+
onClick={() => setChartSource('local')}
|
|
102
|
+
className={clsx(
|
|
103
|
+
'flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-colors',
|
|
104
|
+
chartSource === 'local'
|
|
105
|
+
? `${SEVERITY_BADGE.info} font-medium`
|
|
106
|
+
: 'text-theme-text-secondary hover:text-theme-text-primary'
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
<Database className="w-3.5 h-3.5" />
|
|
110
|
+
My Repos
|
|
111
|
+
</button>
|
|
112
|
+
<button
|
|
113
|
+
onClick={() => setChartSource('artifacthub')}
|
|
114
|
+
className={clsx(
|
|
115
|
+
'flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-colors',
|
|
116
|
+
chartSource === 'artifacthub'
|
|
117
|
+
? `${SEVERITY_BADGE.info} font-medium`
|
|
118
|
+
: 'text-theme-text-secondary hover:text-theme-text-primary'
|
|
119
|
+
)}
|
|
120
|
+
>
|
|
121
|
+
<Globe className="w-3.5 h-3.5" />
|
|
122
|
+
ArtifactHub
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Repository filter dropdown (only for local) */}
|
|
127
|
+
{chartSource === 'local' && (
|
|
128
|
+
<div className="relative">
|
|
129
|
+
<button
|
|
130
|
+
onClick={() => setRepoDropdownOpen(!repoDropdownOpen)}
|
|
131
|
+
className="flex items-center gap-2 px-3 py-2 bg-theme-elevated border border-theme-border-light rounded-lg text-sm text-theme-text-primary hover:bg-theme-hover transition-colors"
|
|
132
|
+
>
|
|
133
|
+
<Database className="w-4 h-4 text-theme-text-tertiary" />
|
|
134
|
+
<span>{selectedRepo === 'all' ? 'All Repositories' : selectedRepo}</span>
|
|
135
|
+
<ChevronDown className={clsx('w-4 h-4 text-theme-text-tertiary transition-transform', repoDropdownOpen && 'rotate-180')} />
|
|
136
|
+
</button>
|
|
137
|
+
|
|
138
|
+
{repoDropdownOpen && (
|
|
139
|
+
<div className="absolute top-full left-0 mt-1 w-64 bg-theme-surface border border-theme-border rounded-lg shadow-xl z-50 py-1 max-h-64 overflow-auto">
|
|
140
|
+
<button
|
|
141
|
+
onClick={() => { setSelectedRepo('all'); setRepoDropdownOpen(false) }}
|
|
142
|
+
className={clsx(
|
|
143
|
+
'w-full px-3 py-2 text-left text-sm hover:bg-theme-hover flex items-center justify-between',
|
|
144
|
+
selectedRepo === 'all' ? 'text-blue-400' : 'text-theme-text-primary'
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
<span>All Repositories</span>
|
|
148
|
+
{selectedRepo === 'all' && <span className="text-xs">✓</span>}
|
|
149
|
+
</button>
|
|
150
|
+
<div className="border-t border-theme-border my-1" />
|
|
151
|
+
{reposLoading ? (
|
|
152
|
+
<div className="px-3 py-2 text-sm text-theme-text-tertiary">Loading...</div>
|
|
153
|
+
) : repositories?.length === 0 ? (
|
|
154
|
+
<div className="px-3 py-2 text-sm text-theme-text-tertiary">No repositories configured</div>
|
|
155
|
+
) : (
|
|
156
|
+
repositories?.map(repo => (
|
|
157
|
+
<RepoDropdownItem
|
|
158
|
+
key={repo.name}
|
|
159
|
+
repo={repo}
|
|
160
|
+
isSelected={selectedRepo === repo.name}
|
|
161
|
+
onSelect={() => { setSelectedRepo(repo.name); setRepoDropdownOpen(false) }}
|
|
162
|
+
onUpdate={() => handleUpdateRepo(repo.name)}
|
|
163
|
+
isUpdating={updateRepoMutation.isPending}
|
|
164
|
+
canUpdate={canHelmWrite}
|
|
165
|
+
/>
|
|
166
|
+
))
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
|
|
173
|
+
{/* ArtifactHub filters */}
|
|
174
|
+
{chartSource === 'artifacthub' && (
|
|
175
|
+
<div className="flex items-center gap-3">
|
|
176
|
+
<label className="flex items-center gap-1.5 text-sm text-theme-text-secondary">
|
|
177
|
+
<input
|
|
178
|
+
type="checkbox"
|
|
179
|
+
checked={showOfficialOnly}
|
|
180
|
+
onChange={(e) => setShowOfficialOnly(e.target.checked)}
|
|
181
|
+
className="rounded border-theme-border text-blue-500 focus:ring-blue-500"
|
|
182
|
+
/>
|
|
183
|
+
<BadgeCheck className="w-3.5 h-3.5 text-blue-400" />
|
|
184
|
+
Official
|
|
185
|
+
</label>
|
|
186
|
+
<label className="flex items-center gap-1.5 text-sm text-theme-text-secondary">
|
|
187
|
+
<input
|
|
188
|
+
type="checkbox"
|
|
189
|
+
checked={showVerifiedOnly}
|
|
190
|
+
onChange={(e) => setShowVerifiedOnly(e.target.checked)}
|
|
191
|
+
className="rounded border-theme-border text-blue-500 focus:ring-blue-500"
|
|
192
|
+
/>
|
|
193
|
+
<Shield className="w-3.5 h-3.5 text-green-400" />
|
|
194
|
+
Verified
|
|
195
|
+
</label>
|
|
196
|
+
{/* Sort dropdown */}
|
|
197
|
+
<div className="flex items-center gap-1.5">
|
|
198
|
+
<ArrowUpDown className="w-3.5 h-3.5 text-theme-text-tertiary" />
|
|
199
|
+
<select
|
|
200
|
+
value={artifactHubSort}
|
|
201
|
+
onChange={(e) => setArtifactHubSort(e.target.value as ArtifactHubSortOption)}
|
|
202
|
+
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"
|
|
203
|
+
>
|
|
204
|
+
<option value="relevance">Relevance</option>
|
|
205
|
+
<option value="stars">Stars</option>
|
|
206
|
+
<option value="last_updated">Last Updated</option>
|
|
207
|
+
</select>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
|
|
212
|
+
{/* Search */}
|
|
213
|
+
<div className="flex-1 relative">
|
|
214
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-theme-text-tertiary" />
|
|
215
|
+
<input
|
|
216
|
+
type="text"
|
|
217
|
+
placeholder={chartSource === 'local' ? "Search charts..." : "Search ArtifactHub..."}
|
|
218
|
+
value={searchTerm}
|
|
219
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
220
|
+
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"
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
{/* Options (only for local) */}
|
|
225
|
+
{chartSource === 'local' && (
|
|
226
|
+
<>
|
|
227
|
+
<label className="flex items-center gap-2 text-sm text-theme-text-secondary">
|
|
228
|
+
<input
|
|
229
|
+
type="checkbox"
|
|
230
|
+
checked={showAllVersions}
|
|
231
|
+
onChange={(e) => setShowAllVersions(e.target.checked)}
|
|
232
|
+
className="rounded border-theme-border text-blue-500 focus:ring-blue-500"
|
|
233
|
+
/>
|
|
234
|
+
All versions
|
|
235
|
+
</label>
|
|
236
|
+
|
|
237
|
+
{/* Refresh button */}
|
|
238
|
+
<Tooltip content={canHelmWrite ? "Update all repositories" : "Helm write permissions required (rbac.helm=true)"}>
|
|
239
|
+
<button
|
|
240
|
+
onClick={handleUpdateAllRepos}
|
|
241
|
+
disabled={updateRepoMutation.isPending || !canHelmWrite}
|
|
242
|
+
className="p-2 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded-lg disabled:opacity-50"
|
|
243
|
+
>
|
|
244
|
+
<RefreshCw className={clsx('w-4 h-4', updateRepoMutation.isPending && 'animate-spin')} />
|
|
245
|
+
</button>
|
|
246
|
+
</Tooltip>
|
|
247
|
+
</>
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
{/* Chart grid */}
|
|
252
|
+
<div className="flex-1 overflow-auto p-4">
|
|
253
|
+
{isLoading ? (
|
|
254
|
+
<div className="flex items-center justify-center h-32 text-theme-text-tertiary">
|
|
255
|
+
Loading charts...
|
|
256
|
+
</div>
|
|
257
|
+
) : chartSource === 'local' ? (
|
|
258
|
+
// Local charts view
|
|
259
|
+
filteredLocalCharts.length === 0 ? (
|
|
260
|
+
<div className="flex flex-col items-center justify-center h-64 text-theme-text-tertiary gap-3">
|
|
261
|
+
<AlertCircle className="w-12 h-12 text-theme-text-disabled" />
|
|
262
|
+
<div className="text-center">
|
|
263
|
+
<p className="text-lg font-medium text-theme-text-secondary">No charts found</p>
|
|
264
|
+
{searchTerm ? (
|
|
265
|
+
<p className="text-sm mt-1">Try a different search term</p>
|
|
266
|
+
) : repositories?.length === 0 ? (
|
|
267
|
+
<div className="text-sm mt-1">
|
|
268
|
+
<p>No Helm repositories configured.</p>
|
|
269
|
+
<p className="mt-1">
|
|
270
|
+
Add repositories using <code className="bg-theme-elevated px-1 rounded">helm repo add</code>
|
|
271
|
+
</p>
|
|
272
|
+
<p className="mt-2">
|
|
273
|
+
Or try searching on <button onClick={() => setChartSource('artifacthub')} className="text-blue-400 hover:underline">ArtifactHub</button>
|
|
274
|
+
</p>
|
|
275
|
+
</div>
|
|
276
|
+
) : (
|
|
277
|
+
<p className="text-sm mt-1">Try updating your repositories</p>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
) : selectedRepo === 'all' ? (
|
|
282
|
+
// Grouped by repository
|
|
283
|
+
<div className="space-y-6">
|
|
284
|
+
{Array.from(chartsByRepo.entries()).map(([repoName, charts]) => (
|
|
285
|
+
<div key={repoName}>
|
|
286
|
+
<div className="flex items-center gap-2 mb-3">
|
|
287
|
+
<Database className="w-4 h-4 text-theme-text-tertiary" />
|
|
288
|
+
<h3 className="text-sm font-medium text-theme-text-secondary">{repoName}</h3>
|
|
289
|
+
<span className="text-xs text-theme-text-tertiary">({charts.length})</span>
|
|
290
|
+
</div>
|
|
291
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
|
292
|
+
{charts.map((chart, idx) => (
|
|
293
|
+
<LocalChartCard
|
|
294
|
+
key={`${chart.repository}-${chart.name}-${chart.version}-${idx}`}
|
|
295
|
+
chart={chart}
|
|
296
|
+
onSelect={() => onChartSelect(chart.repository, chart.name, chart.version, 'local')}
|
|
297
|
+
/>
|
|
298
|
+
))}
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
))}
|
|
302
|
+
</div>
|
|
303
|
+
) : (
|
|
304
|
+
// Single repository view
|
|
305
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
|
306
|
+
{filteredLocalCharts.map((chart, idx) => (
|
|
307
|
+
<LocalChartCard
|
|
308
|
+
key={`${chart.repository}-${chart.name}-${chart.version}-${idx}`}
|
|
309
|
+
chart={chart}
|
|
310
|
+
onSelect={() => onChartSelect(chart.repository, chart.name, chart.version, 'local')}
|
|
311
|
+
/>
|
|
312
|
+
))}
|
|
313
|
+
</div>
|
|
314
|
+
)
|
|
315
|
+
) : (
|
|
316
|
+
// ArtifactHub view
|
|
317
|
+
!searchTerm ? (
|
|
318
|
+
// No search term yet - show prompt
|
|
319
|
+
<div className="flex flex-col items-center justify-center h-64 text-theme-text-tertiary gap-3">
|
|
320
|
+
<Globe className="w-12 h-12 text-theme-text-disabled" />
|
|
321
|
+
<div className="text-center">
|
|
322
|
+
<p className="text-lg font-medium text-theme-text-secondary">Search ArtifactHub</p>
|
|
323
|
+
<p className="text-sm mt-1">Enter a search term to find charts from the community</p>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
) : !artifactHubResult?.charts.length ? (
|
|
327
|
+
// Searched but no results
|
|
328
|
+
<div className="flex flex-col items-center justify-center h-64 text-theme-text-tertiary gap-3">
|
|
329
|
+
<AlertCircle className="w-12 h-12 text-theme-text-disabled" />
|
|
330
|
+
<div className="text-center">
|
|
331
|
+
<p className="text-lg font-medium text-theme-text-secondary">No charts found</p>
|
|
332
|
+
<p className="text-sm mt-1">Try a different search term or adjust filters</p>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
) : (
|
|
336
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
|
337
|
+
{artifactHubResult.charts.map((chart, idx) => (
|
|
338
|
+
<ArtifactHubChartCard
|
|
339
|
+
key={`${chart.repository.name}-${chart.name}-${chart.version}-${idx}`}
|
|
340
|
+
chart={chart}
|
|
341
|
+
onSelect={() => onChartSelect(chart.repository.name, chart.name, chart.version, 'artifacthub')}
|
|
342
|
+
/>
|
|
343
|
+
))}
|
|
344
|
+
</div>
|
|
345
|
+
)
|
|
346
|
+
)}
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
interface RepoDropdownItemProps {
|
|
353
|
+
repo: HelmRepository
|
|
354
|
+
isSelected: boolean
|
|
355
|
+
onSelect: () => void
|
|
356
|
+
onUpdate: () => void
|
|
357
|
+
isUpdating: boolean
|
|
358
|
+
canUpdate: boolean
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function RepoDropdownItem({ repo, isSelected, onSelect, onUpdate, isUpdating, canUpdate }: RepoDropdownItemProps) {
|
|
362
|
+
return (
|
|
363
|
+
<div className="flex items-center justify-between px-3 py-2 hover:bg-theme-hover group">
|
|
364
|
+
<button
|
|
365
|
+
onClick={onSelect}
|
|
366
|
+
className={clsx(
|
|
367
|
+
'flex-1 text-left text-sm truncate',
|
|
368
|
+
isSelected ? 'text-blue-400' : 'text-theme-text-primary'
|
|
369
|
+
)}
|
|
370
|
+
>
|
|
371
|
+
{repo.name}
|
|
372
|
+
{repo.lastUpdated && (
|
|
373
|
+
<span className="text-xs text-theme-text-tertiary ml-2">
|
|
374
|
+
{formatAge(repo.lastUpdated)}
|
|
375
|
+
</span>
|
|
376
|
+
)}
|
|
377
|
+
</button>
|
|
378
|
+
<button
|
|
379
|
+
onClick={(e) => { e.stopPropagation(); onUpdate() }}
|
|
380
|
+
disabled={isUpdating || !canUpdate}
|
|
381
|
+
className="p-1 text-theme-text-tertiary hover:text-theme-text-primary opacity-0 group-hover:opacity-100 transition-opacity disabled:opacity-50"
|
|
382
|
+
title={canUpdate ? "Update repository" : "Helm write permissions required (rbac.helm=true)"}
|
|
383
|
+
>
|
|
384
|
+
<RefreshCw className={clsx('w-3.5 h-3.5', isUpdating && 'animate-spin')} />
|
|
385
|
+
</button>
|
|
386
|
+
</div>
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
interface LocalChartCardProps {
|
|
391
|
+
chart: ChartInfo
|
|
392
|
+
onSelect: () => void
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function LocalChartCard({ chart, onSelect }: LocalChartCardProps) {
|
|
396
|
+
return (
|
|
397
|
+
<button
|
|
398
|
+
onClick={onSelect}
|
|
399
|
+
className="flex flex-col p-3 bg-theme-elevated/30 hover:bg-theme-elevated/50 border border-theme-border-light rounded-lg text-left transition-colors group"
|
|
400
|
+
>
|
|
401
|
+
<div className="flex items-start gap-3">
|
|
402
|
+
{chart.icon ? (
|
|
403
|
+
<img
|
|
404
|
+
src={chart.icon}
|
|
405
|
+
alt=""
|
|
406
|
+
className="w-10 h-10 rounded object-contain bg-white/10 p-1"
|
|
407
|
+
onError={(e) => {
|
|
408
|
+
(e.target as HTMLImageElement).style.display = 'none'
|
|
409
|
+
}}
|
|
410
|
+
/>
|
|
411
|
+
) : (
|
|
412
|
+
<div className="w-10 h-10 rounded bg-theme-hover flex items-center justify-center">
|
|
413
|
+
<Package className="w-5 h-5 text-theme-text-tertiary" />
|
|
414
|
+
</div>
|
|
415
|
+
)}
|
|
416
|
+
<div className="flex-1 min-w-0">
|
|
417
|
+
<div className="flex items-center gap-2">
|
|
418
|
+
<h4 className="text-sm font-medium text-theme-text-primary truncate">{chart.name}</h4>
|
|
419
|
+
{chart.deprecated && (
|
|
420
|
+
<span className={clsx('px-1 py-0.5 text-[10px] rounded', SEVERITY_BADGE.warning)}>
|
|
421
|
+
deprecated
|
|
422
|
+
</span>
|
|
423
|
+
)}
|
|
424
|
+
</div>
|
|
425
|
+
<div className="flex items-center gap-2 mt-0.5">
|
|
426
|
+
<span className="text-xs text-theme-text-tertiary">{chart.version}</span>
|
|
427
|
+
{chart.appVersion && (
|
|
428
|
+
<>
|
|
429
|
+
<span className="text-xs text-theme-text-disabled">|</span>
|
|
430
|
+
<span className="text-xs text-theme-text-tertiary">App: {chart.appVersion}</span>
|
|
431
|
+
</>
|
|
432
|
+
)}
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
<ExternalLink className="w-4 h-4 text-theme-text-tertiary opacity-0 group-hover:opacity-100 shrink-0" />
|
|
436
|
+
</div>
|
|
437
|
+
{chart.description && (
|
|
438
|
+
<p className="mt-2 text-xs text-theme-text-secondary line-clamp-2">
|
|
439
|
+
{chart.description}
|
|
440
|
+
</p>
|
|
441
|
+
)}
|
|
442
|
+
</button>
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
interface ArtifactHubChartCardProps {
|
|
447
|
+
chart: ArtifactHubChart
|
|
448
|
+
onSelect: () => void
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function ArtifactHubChartCard({ chart, onSelect }: ArtifactHubChartCardProps) {
|
|
452
|
+
// Format Unix timestamp to relative age
|
|
453
|
+
const lastUpdated = chart.updatedAt
|
|
454
|
+
? formatAge(new Date(chart.updatedAt * 1000).toISOString())
|
|
455
|
+
: null
|
|
456
|
+
|
|
457
|
+
return (
|
|
458
|
+
<button
|
|
459
|
+
onClick={onSelect}
|
|
460
|
+
className="flex flex-col p-4 bg-theme-elevated/30 hover:bg-theme-elevated/50 border border-theme-border-light rounded-lg text-left transition-colors group"
|
|
461
|
+
>
|
|
462
|
+
{/* Header row */}
|
|
463
|
+
<div className="flex items-start gap-3">
|
|
464
|
+
{/* Logo */}
|
|
465
|
+
{chart.logoUrl ? (
|
|
466
|
+
<img
|
|
467
|
+
src={chart.logoUrl}
|
|
468
|
+
alt=""
|
|
469
|
+
className="w-12 h-12 rounded object-contain bg-white/10 p-1 shrink-0"
|
|
470
|
+
onError={(e) => {
|
|
471
|
+
(e.target as HTMLImageElement).style.display = 'none'
|
|
472
|
+
}}
|
|
473
|
+
/>
|
|
474
|
+
) : (
|
|
475
|
+
<div className="w-12 h-12 rounded bg-theme-hover flex items-center justify-center shrink-0">
|
|
476
|
+
<Package className="w-6 h-6 text-theme-text-tertiary" />
|
|
477
|
+
</div>
|
|
478
|
+
)}
|
|
479
|
+
|
|
480
|
+
{/* Name and org */}
|
|
481
|
+
<div className="flex-1 min-w-0">
|
|
482
|
+
<h4 className="text-sm font-medium text-theme-text-primary truncate">{chart.name}</h4>
|
|
483
|
+
<div className="flex items-center gap-2 mt-0.5 text-xs text-theme-text-tertiary">
|
|
484
|
+
<span className="flex items-center gap-1">
|
|
485
|
+
<Building2 className="w-3 h-3" />
|
|
486
|
+
{chart.repository.organizationName || chart.repository.name}
|
|
487
|
+
</span>
|
|
488
|
+
<span className="flex items-center gap-1">
|
|
489
|
+
<Globe className="w-3 h-3" />
|
|
490
|
+
{chart.repository.name}
|
|
491
|
+
</span>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
|
|
495
|
+
{/* Stats - top right */}
|
|
496
|
+
<div className="text-right shrink-0">
|
|
497
|
+
<div className="flex items-center justify-end gap-2">
|
|
498
|
+
{chart.stars > 0 && (
|
|
499
|
+
<span className="flex items-center gap-1 px-2 py-0.5 text-xs border border-theme-border rounded">
|
|
500
|
+
<Star className="w-3 h-3" />
|
|
501
|
+
{chart.stars}
|
|
502
|
+
</span>
|
|
503
|
+
)}
|
|
504
|
+
</div>
|
|
505
|
+
{lastUpdated && (
|
|
506
|
+
<p className="text-xs text-theme-text-tertiary mt-1">Updated {lastUpdated}</p>
|
|
507
|
+
)}
|
|
508
|
+
<p className="text-xs text-theme-text-secondary mt-0.5">Version {chart.version}</p>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
|
|
512
|
+
{/* Description */}
|
|
513
|
+
{chart.description && (
|
|
514
|
+
<p className="mt-3 text-xs text-theme-text-secondary line-clamp-2">
|
|
515
|
+
{chart.description}
|
|
516
|
+
</p>
|
|
517
|
+
)}
|
|
518
|
+
|
|
519
|
+
{/* Footer - badges row */}
|
|
520
|
+
<div className="flex items-center justify-between mt-3 pt-3 border-t border-theme-border-light/50">
|
|
521
|
+
{/* Keywords/category */}
|
|
522
|
+
<div className="flex items-center gap-2">
|
|
523
|
+
{chart.deprecated && (
|
|
524
|
+
<span className={clsx('px-2 py-0.5 text-[10px] rounded', SEVERITY_BADGE.warning)}>
|
|
525
|
+
deprecated
|
|
526
|
+
</span>
|
|
527
|
+
)}
|
|
528
|
+
{chart.keywords && chart.keywords.length > 0 && (
|
|
529
|
+
<span className="px-2 py-0.5 text-[10px] rounded bg-theme-elevated text-theme-text-tertiary border border-theme-border-light truncate max-w-[150px]">
|
|
530
|
+
{chart.keywords[0]}
|
|
531
|
+
</span>
|
|
532
|
+
)}
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
{/* Feature badges */}
|
|
536
|
+
<div className="flex items-center gap-1">
|
|
537
|
+
{/* Values Schema */}
|
|
538
|
+
<Tooltip content={chart.hasValuesSchema ? 'Has values schema' : 'No values schema'}>
|
|
539
|
+
<span className={clsx(
|
|
540
|
+
'p-1.5 rounded',
|
|
541
|
+
chart.hasValuesSchema ? SEVERITY_BADGE.warning : 'bg-theme-elevated/50 text-theme-text-disabled'
|
|
542
|
+
)}>
|
|
543
|
+
<FileJson className="w-4 h-4" />
|
|
544
|
+
</span>
|
|
545
|
+
</Tooltip>
|
|
546
|
+
|
|
547
|
+
{/* Signed */}
|
|
548
|
+
<Tooltip content={chart.signed ? 'Signed package' : 'Not signed'}>
|
|
549
|
+
<span className={clsx(
|
|
550
|
+
'p-1.5 rounded',
|
|
551
|
+
chart.signed ? SEVERITY_BADGE.info : 'bg-theme-elevated/50 text-theme-text-disabled'
|
|
552
|
+
)}>
|
|
553
|
+
<PenTool className="w-4 h-4" />
|
|
554
|
+
</span>
|
|
555
|
+
</Tooltip>
|
|
556
|
+
|
|
557
|
+
{/* Verified Publisher */}
|
|
558
|
+
<Tooltip content={chart.repository.verifiedPublisher ? 'Verified publisher' : 'Not verified'}>
|
|
559
|
+
<span className={clsx(
|
|
560
|
+
'p-1.5 rounded',
|
|
561
|
+
chart.repository.verifiedPublisher ? SEVERITY_BADGE.success : 'bg-theme-elevated/50 text-theme-text-disabled'
|
|
562
|
+
)}>
|
|
563
|
+
<Shield className="w-4 h-4" />
|
|
564
|
+
</span>
|
|
565
|
+
</Tooltip>
|
|
566
|
+
|
|
567
|
+
{/* Official */}
|
|
568
|
+
<Tooltip content={chart.repository.official ? 'Official package' : 'Community package'}>
|
|
569
|
+
<span className={clsx(
|
|
570
|
+
'p-1.5 rounded',
|
|
571
|
+
chart.repository.official ? SEVERITY_BADGE.success : 'bg-theme-elevated/50 text-theme-text-disabled'
|
|
572
|
+
)}>
|
|
573
|
+
<Star className={clsx('w-4 h-4', chart.repository.official && 'fill-current')} />
|
|
574
|
+
</span>
|
|
575
|
+
</Tooltip>
|
|
576
|
+
</div>
|
|
577
|
+
</div>
|
|
578
|
+
</button>
|
|
579
|
+
)
|
|
580
|
+
}
|