/** * Port Forwarding Rule Card * Displays a single port forwarding rule in grid or list view */ import { Copy,Loader2,Pencil,Play,Square,Trash2 } from 'lucide-react'; import React from 'react'; import { useI18n } from '../../application/i18n/I18nProvider'; import { Host, PortForwardingRule } from '../../domain/models'; import { cn } from '../../lib/utils'; import { Button } from '../ui/button'; import { ContextMenu,ContextMenuContent,ContextMenuItem,ContextMenuSeparator,ContextMenuTrigger } from '../ui/context-menu'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip'; import { vaultEntityIconClass } from '../vault/VaultEntityIcon'; import { buildRuleSummary,getStatusColor,getTypeColor } from './utils'; export type ViewMode = 'grid' | 'list'; export interface RuleCardProps { rule: PortForwardingRule; host?: Host; // The relay host for this rule (for tooltip display) viewMode: ViewMode; isSelected: boolean; isPending: boolean; canStop: boolean; reorderProps?: React.HTMLAttributes; onSelect: () => void; onEdit: () => void; onDuplicate: () => void; onDelete: () => void; onStart: () => void; onStop: () => void; } export const RuleCard: React.FC = ({ rule, host, viewMode, isSelected, isPending, canStop, reorderProps, onSelect, onEdit, onDuplicate, onDelete, onStart, onStop, }) => { const { t } = useI18n(); const isActive = rule.status === 'active'; const isStoppable = canStop || rule.status === 'active' || rule.status === 'connecting'; // unknown/stale means we cannot trust inactive — do not offer Start until // an authoritative snapshot confirms there is no runtime. const isStartable = !isStoppable && (rule.status === 'inactive' || rule.status === 'error'); return (
{rule.type[0].toUpperCase()}
{rule.label} {rule.status === 'error' && rule.error ? ( {rule.error} ) : ( )}
{buildRuleSummary(t, rule)}
{host ? ( <>
{t('pf.tooltip.relayHost')}
{t('pf.tooltip.hostLabel')}: {host.label}
{t('pf.tooltip.hostAddress')}: {host.username}@{host.hostname}:{host.port}
) : (
{t('pf.tooltip.noHost')}
)}
{rule.type === 'dynamic' ? t('pf.tooltip.dynamicDesc') : rule.type === 'local' ? t('pf.tooltip.localDesc') : t('pf.tooltip.remoteDesc') }
{isPending ? ( ) : isStartable ? ( ) : isStoppable ? ( ) : null}
{t('action.edit')} {t('action.duplicate')} {isStartable && ( {t('action.start')} )} {isStoppable && ( {t('action.stop')} )} {t('action.delete')}
); }; export default RuleCard;