import { Bookmark, ChevronDown, CircleUserRound, Server, Terminal, Trash2, Usb, } from "lucide-react"; import React, { memo, useCallback, useMemo, useState } from "react"; import { useI18n } from "../application/i18n/I18nProvider"; import { resolveHostIconAppearance } from "../domain/hostIcon"; import { cn } from "../lib/utils"; import { ConnectionLog, Host } from "../types"; import { DistroAvatar } from "./DistroAvatar"; import { ScrollArea } from "./ui/scroll-area"; import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; interface ConnectionLogsManagerProps { logs: ConnectionLog[]; hosts: Host[]; onToggleSaved: (id: string) => void; onDelete: (id: string) => void; onClearUnsaved: () => void; onOpenLogView: (log: ConnectionLog) => void; } // Format date for display const formatDate = (timestamp: number, locale: string) => { const date = new Date(timestamp); return date.toLocaleDateString(locale || undefined, { year: "numeric", month: "short", day: "numeric", }); }; // Format time range const formatTimeRange = (start: number, end: number | undefined, locale: string, ongoingLabel: string) => { const startDate = new Date(start); const startTime = startDate.toLocaleTimeString(locale || undefined, { hour: "2-digit", minute: "2-digit", }); if (!end) { return `${startTime} - ${ongoingLabel}`; } const endDate = new Date(end); const endTime = endDate.toLocaleTimeString(locale || undefined, { hour: "2-digit", minute: "2-digit", }); return `${startTime} - ${endTime}`; }; // Log Item Component interface LogItemProps { log: ConnectionLog; onToggleSaved: (id: string) => void; onDelete: (id: string) => void; onClick: () => void; } const LogItem = memo(({ log, onToggleSaved, onDelete, onClick }) => { const { t, resolvedLocale } = useI18n(); const isLocal = log.protocol === "local" || log.hostname === "localhost"; const isSerial = log.protocol === "serial"; const customHostIcon = resolveHostIconAppearance({ iconMode: log.hostIconMode, iconId: log.hostIconId, iconColorMode: log.hostIconColorMode, iconColor: log.hostIconColor, iconColorCustom: log.hostIconColorCustom, }); const hasPersistedHostIcon = !isLocal && !isSerial && (!!log.hostDistro || !!customHostIcon); return (
{/* Date column */}
{formatDate(log.startTime, resolvedLocale)}
{formatTimeRange(log.startTime, log.endTime, resolvedLocale, t("logs.ongoing"))}
{/* User column */}
{log.localUsername}
{log.localHostname}
{/* Host column */}
{hasPersistedHostIcon ? ( ) : (
{isSerial ? : isLocal ? : }
)}
{isLocal ? t("logs.localTerminal") : log.hostLabel}
{isLocal ? "local" : isSerial ? `serial, ${log.hostname}` : `${log.protocol}, ${log.username}`}
{/* Saved column */}
{log.saved ? t("logs.action.unsave") : t("logs.action.save")} {t("logs.action.delete")}
); }); LogItem.displayName = "LogItem"; const ConnectionLogsManager: React.FC = ({ logs, hosts: _hosts, onToggleSaved, onDelete, onClearUnsaved: _onClearUnsaved, onOpenLogView, }) => { const { t } = useI18n(); const INITIAL_RENDER_LIMIT = 30; const LOAD_MORE_COUNT = 30; // Track how many items to show const [renderLimit, setRenderLimit] = useState(INITIAL_RENDER_LIMIT); // Sort logs by newest first const filteredLogs = useMemo(() => { return [...logs].sort((a, b) => b.startTime - a.startTime); }, [logs]); const displayedLogs = useMemo(() => { return filteredLogs.slice(0, renderLimit); }, [filteredLogs, renderLimit]); const hasMore = filteredLogs.length > renderLimit; const handleLoadMore = useCallback(() => { setRenderLimit(prev => prev + LOAD_MORE_COUNT); }, []); const handleToggleSaved = useCallback( (id: string) => onToggleSaved(id), [onToggleSaved], ); const handleDelete = useCallback( (id: string) => onDelete(id), [onDelete], ); // Rendered items const renderedItems = useMemo(() => { return displayedLogs.map((log) => ( onOpenLogView(log)} /> )); }, [displayedLogs, handleToggleSaved, handleDelete, onOpenLogView]); return (
{/* Table Header */} {displayedLogs.length > 0 && (
{t("logs.table.date")}
{t("logs.table.user")}
{t("logs.table.host")}
{t("logs.table.saved")}
)} {/* Content */}
{displayedLogs.length === 0 ? (

{t("logs.empty.title")}

{t("logs.empty.desc")}

) : ( <> {renderedItems} {hasMore && ( )} )}
); }; // Custom comparison const logsManagerAreEqual = ( prev: ConnectionLogsManagerProps, next: ConnectionLogsManagerProps, ): boolean => { return prev.logs === next.logs && prev.hosts === next.hosts; }; export default memo(ConnectionLogsManager, logsManagerAreEqual);