import React, { useCallback, useEffect, useState } from "react"; import { Plus, RefreshCw, Search, ChevronDown, ChevronRight } from "lucide-react"; import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, Input, Label, Badge, } from "@/components/ui"; import { helmListReposCmd, helmSearchRepoCmd, helmAddRepoCmd, helmUpdateReposCmd, } from "@/lib/tauriCommands"; import type { HelmRepository, HelmChart } from "@/lib/tauriCommands"; interface HelmChartListProps { clusterId: string; } export function HelmChartList({ clusterId }: HelmChartListProps) { const [repos, setRepos] = useState([]); const [charts, setCharts] = useState([]); const [selectedRepo, setSelectedRepo] = useState(null); const [search, setSearch] = useState(""); const [loading, setLoading] = useState(false); const [updatingRepos, setUpdatingRepos] = useState(false); const [error, setError] = useState(null); const [expandedChart, setExpandedChart] = useState(null); const [addRepoOpen, setAddRepoOpen] = useState(false); const [newRepoName, setNewRepoName] = useState(""); const [newRepoUrl, setNewRepoUrl] = useState(""); const [addingRepo, setAddingRepo] = useState(false); const [addRepoError, setAddRepoError] = useState(null); const loadData = useCallback(async () => { setLoading(true); setError(null); try { const repoList = await helmListReposCmd(clusterId); setRepos(repoList); const chartList = await helmSearchRepoCmd(clusterId, ""); setCharts(chartList); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setLoading(false); } }, [clusterId]); useEffect(() => { void loadData(); }, [loadData]); const handleUpdateRepos = async () => { setUpdatingRepos(true); setError(null); try { await helmUpdateReposCmd(clusterId); await loadData(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setUpdatingRepos(false); } }; const handleAddRepo = async () => { if (!newRepoName.trim() || !newRepoUrl.trim()) return; setAddingRepo(true); setAddRepoError(null); try { await helmAddRepoCmd(clusterId, newRepoName.trim(), newRepoUrl.trim()); setAddRepoOpen(false); setNewRepoName(""); setNewRepoUrl(""); await loadData(); } catch (err) { setAddRepoError(err instanceof Error ? err.message : String(err)); } finally { setAddingRepo(false); } }; const filteredCharts = charts.filter((c) => { const matchesRepo = selectedRepo == null || c.repository === selectedRepo; const matchesSearch = search.trim() === "" || c.name.toLowerCase().includes(search.toLowerCase()) || c.description.toLowerCase().includes(search.toLowerCase()); return matchesRepo && matchesSearch; }); return (
{/* Toolbar */}
setSearch(e.target.value)} className="pl-9" />
{error && (
{error}
)}
{/* Repository sidebar */}
Repositories
setSelectedRepo(null)} > All repositories
{repos.map((repo) => (
setSelectedRepo(repo.name)} > {repo.name}
))} {repos.length === 0 && !loading && (
No repos
)}
{/* Charts table */}
{loading ? (
Loading charts…
) : repos.length === 0 ? (

No helm repositories configured.

Add a repository to get started.

) : filteredCharts.length === 0 ? (
No charts match your search.
) : ( {filteredCharts.map((chart) => { const key = `${chart.repository}/${chart.name}`; const isExpanded = expandedChart === key; return ( setExpandedChart(isExpanded ? null : key)} > {isExpanded && ( )} ); })}
Name Version App Version Repository Description
{isExpanded ? ( ) : ( )} {chart.name.includes("/") ? chart.name.split("/").slice(1).join("/") : chart.name}
{chart.chart_version} {chart.app_version || "—"} {chart.repository} {chart.description || "—"}
{chart.repository}/{chart.name}
{chart.description || "No description available."}
Chart: {chart.chart_version} {chart.app_version && App: {chart.app_version}}
)}
{/* Add Repository Dialog */} Add Helm Repository
setNewRepoName(e.target.value)} />
setNewRepoUrl(e.target.value)} />
{addRepoError && (
{addRepoError}
)}
); }