/** * Proxy Configuration Sub-Panel * Panel for configuring HTTP/SOCKS5/ProxyCommand proxy settings */ import { Globe, KeyRound, SquareTerminal, Trash2 } from 'lucide-react'; import React, { useCallback, useMemo } from 'react'; import { useI18n } from '../../application/i18n/I18nProvider'; import { formatProxyConfigEndpoint, formatProxyConfigType, hasIncompleteProxyIdentity, hasMissingProxyIdentity, hasUnreadableProxyCredential, isProxyCommandConfig, isValidProxyPort, } from '../../domain/proxyProfiles'; import { Identity, ProxyConfig, ProxyProfile } from '../../types'; import { AsidePanel, AsidePanelContent, type AsidePanelLayout, type AsidePanelResizeProps } from '../ui/aside-panel'; import { Badge } from '../ui/badge'; import { Button } from '../ui/button'; import { Card } from '../ui/card'; import { Input } from '../ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; export interface ProxyPanelProps { proxyConfig?: ProxyConfig; proxyProfiles?: ProxyProfile[]; identities?: Identity[]; selectedProxyProfileId?: string; onUpdateProxy: (field: keyof ProxyConfig, value: ProxyConfig[keyof ProxyConfig]) => void; onSelectProxyProfile?: (profileId: string | undefined) => void; onClearProxy: () => void; onBack: () => void; onCancel: () => void; layout?: AsidePanelLayout; } export type ProxyPanelPropsWithResize = ProxyPanelProps & AsidePanelResizeProps; export const ProxyPanel: React.FC = ({ proxyConfig, proxyProfiles = [], identities = [], selectedProxyProfileId, onUpdateProxy, onSelectProxyProfile, onClearProxy, onBack, onCancel, layout = 'overlay', resizable, persistWidthStorageKey, resizeAriaLabel, }) => { const { t } = useI18n(); const customValue = '__custom__'; const selectedProfile = useMemo( () => proxyProfiles.find((profile) => profile.id === selectedProxyProfileId), [proxyProfiles, selectedProxyProfileId], ); const hasMissingProfile = Boolean(selectedProxyProfileId && !selectedProfile); const selectedValue = selectedProfile ? selectedProfile.id : customValue; const isUsingProfile = Boolean(selectedProfile); const isCommandProxy = isProxyCommandConfig(proxyConfig); const hasManualProxyHost = Boolean(proxyConfig?.host?.trim()); const hasManualProxyCommand = Boolean(proxyConfig?.command?.trim()); const hasManualProxyValue = isCommandProxy ? hasManualProxyCommand : hasManualProxyHost; const hasInvalidManualProxyPort = !isCommandProxy && hasManualProxyHost && !isValidProxyPort(proxyConfig?.port); const effectiveProxyConfig = selectedProfile?.config ?? proxyConfig; const hasMissingIdentity = hasMissingProxyIdentity(effectiveProxyConfig, identities); const hasIncompleteIdentity = hasIncompleteProxyIdentity(effectiveProxyConfig, identities); const hasUnreadableIdentity = hasUnreadableProxyCredential(effectiveProxyConfig, identities); const hasInvalidIdentity = hasMissingIdentity || hasIncompleteIdentity || hasUnreadableIdentity; const canSave = (isUsingProfile && !hasInvalidIdentity) || (!isUsingProfile && hasManualProxyValue && !hasInvalidManualProxyPort && !hasInvalidIdentity); const manualCredentialsValue = '__manual_credentials__'; const missingIdentityValue = '__missing_identity__'; const selectedIdentity = useMemo( () => identities.find((identity) => identity.id === proxyConfig?.identityId), [identities, proxyConfig?.identityId], ); const selectedIdentityValue = selectedIdentity?.id || (hasMissingIdentity ? missingIdentityValue : manualCredentialsValue); const handleBack = useCallback(() => { if (hasInvalidManualProxyPort || hasInvalidIdentity) return; onBack(); }, [hasInvalidManualProxyPort, hasInvalidIdentity, onBack]); return ( {t('common.save')} } > {(proxyProfiles.length > 0 || hasMissingProfile) && onSelectProxyProfile && (

{t('hostDetails.proxyPanel.savedProxy')}

{hasMissingProfile && (
{t('hostDetails.proxyPanel.missingSaved')}
)} {selectedProfile && (
{formatProxyConfigType(selectedProfile.config)} {formatProxyConfigEndpoint(selectedProfile.config)}
)} {selectedProfile && hasMissingIdentity && (
{t('hostDetails.proxyPanel.missingIdentity')}
)} {selectedProfile && hasIncompleteIdentity && (
{t('hostDetails.proxyPanel.incompleteIdentity')}
)} {selectedProfile && hasUnreadableIdentity && (
{t('hostDetails.proxyPanel.unreadableIdentity')}
)}
)} {!isUsingProfile && ( <>

{t('field.type')}

{isCommandProxy ? (
{t('hostDetails.proxyPanel.commandHelp')}
onUpdateProxy('command', e.target.value)} className="h-10 font-mono text-xs" />
) : (
onUpdateProxy('host', e.target.value)} className="h-10 flex-1" />
{t('hostDetails.port')} onUpdateProxy('port', parseInt(e.target.value) || 0)} className="h-10 w-20 text-center" />
)} {hasInvalidManualProxyPort && (

{t('proxyProfiles.error.port')}

)}
{!isCommandProxy &&

{t('hostDetails.proxyPanel.credentials')}

{t('common.optional')}
{identities.length > 0 && (

{t('hostDetails.proxyPanel.keychainIdentity')}

)} {hasMissingIdentity && (
{t('hostDetails.proxyPanel.missingIdentity')}
)} {hasIncompleteIdentity && (
{t('hostDetails.proxyPanel.incompleteIdentity')}
)} {hasUnreadableIdentity && (
{t('hostDetails.proxyPanel.unreadableIdentity')}
)} {selectedIdentity ? (
{t('hostDetails.proxyPanel.keychainIdentity')} {selectedIdentity.label} - {selectedIdentity.username}
) : ( <> onUpdateProxy('username', e.target.value)} className="h-10" /> onUpdateProxy('password', e.target.value)} className="h-10" /> )}
} )} {(proxyConfig?.host || proxyConfig?.command || selectedProxyProfileId) && ( )}
); }; export default ProxyPanel;