import React, { useState, useEffect, useMemo } from 'react'; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Search, Key, Folder, Edit, Trash2, Shield, Pin, Tag, Info } from 'lucide-react'; import { getCredentials, deleteCredential } from '@/ui/main-axios'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; import {CredentialEditor} from './CredentialEditor'; import CredentialViewer from './CredentialViewer'; import type { Credential, CredentialsManagerProps } from '../../../types/index.js'; export function CredentialsManager({ onEditCredential }: CredentialsManagerProps) { const { t } = useTranslation(); const [credentials, setCredentials] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [showViewer, setShowViewer] = useState(false); const [viewingCredential, setViewingCredential] = useState(null); useEffect(() => { fetchCredentials(); }, []); const fetchCredentials = async () => { try { setLoading(true); const data = await getCredentials(); setCredentials(data); setError(null); } catch (err) { setError(t('credentials.failedToFetchCredentials')); } finally { setLoading(false); } }; const handleEdit = (credential: Credential) => { if (onEditCredential) { onEditCredential(credential); } }; const handleDelete = async (credentialId: number, credentialName: string) => { if (window.confirm(t('credentials.confirmDeleteCredential', { name: credentialName }))) { try { await deleteCredential(credentialId); toast.success(t('credentials.credentialDeletedSuccessfully', { name: credentialName })); await fetchCredentials(); window.dispatchEvent(new CustomEvent('credentials:changed')); } catch (err: any) { if (err.response?.data?.details) { toast.error(`${err.response.data.error}\n${err.response.data.details}`); } else { toast.error(t('credentials.failedToDeleteCredential')); } } } }; const filteredAndSortedCredentials = useMemo(() => { let filtered = credentials; if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); filtered = credentials.filter(credential => { const searchableText = [ credential.name || '', credential.username, credential.description || '', ...(credential.tags || []), credential.authType, credential.keyType || '' ].join(' ').toLowerCase(); return searchableText.includes(query); }); } return filtered.sort((a, b) => { const aName = a.name || a.username; const bName = b.name || b.username; return aName.localeCompare(bName); }); }, [credentials, searchQuery]); const credentialsByFolder = useMemo(() => { const grouped: { [key: string]: Credential[] } = {}; filteredAndSortedCredentials.forEach(credential => { const folder = credential.folder || t('credentials.uncategorized'); if (!grouped[folder]) { grouped[folder] = []; } grouped[folder].push(credential); }); const sortedFolders = Object.keys(grouped).sort((a, b) => { if (a === t('credentials.uncategorized')) return -1; if (b === t('credentials.uncategorized')) return 1; return a.localeCompare(b); }); const sortedGrouped: { [key: string]: Credential[] } = {}; sortedFolders.forEach(folder => { sortedGrouped[folder] = grouped[folder]; }); return sortedGrouped; }, [filteredAndSortedCredentials, t]); if (loading) { return (

{t('credentials.loadingCredentials')}

); } if (error) { return (

{error}

); } if (credentials.length === 0) { return (

{t('credentials.noCredentials')}

{t('credentials.noCredentialsMessage')}

); } return (

{t('credentials.sshCredentials')}

{t('credentials.credentialsCount', { count: filteredAndSortedCredentials.length })}

setSearchQuery(e.target.value)} className="pl-10" />
{Object.entries(credentialsByFolder).map(([folder, folderCredentials]) => (
{folder} {folderCredentials.length}
{folderCredentials.map((credential) => (
handleEdit(credential)} >

{credential.name || `${credential.username}`}

{credential.username}

{credential.authType === 'password' ? t('credentials.password') : t('credentials.sshKey')}

{credential.tags && credential.tags.length > 0 && (
{credential.tags.slice(0, 6).map((tag, index) => ( {tag} ))} {credential.tags.length > 6 && ( +{credential.tags.length - 6} )}
)}
{credential.authType === 'password' ? ( ) : ( )} {credential.authType} {credential.authType === 'key' && credential.keyType && ( {credential.keyType} )}
))}
))}
{showViewer && viewingCredential && ( setShowViewer(false)} onEdit={() => { setShowViewer(false); handleEdit(viewingCredential); }} /> )}
); }