'use client'; import { useState, useEffect } from 'react'; import type { CreateServerData } from '../../types/server'; import { Button } from './ui/button'; import { SSHKeyInput } from './SSHKeyInput'; import { PublicKeyModal } from './PublicKeyModal'; import { Key } from 'lucide-react'; interface ServerFormProps { onSubmit: (data: CreateServerData) => void; initialData?: CreateServerData; isEditing?: boolean; onCancel?: () => void; } export function ServerForm({ onSubmit, initialData, isEditing = false, onCancel }: ServerFormProps) { const [formData, setFormData] = useState( initialData ?? { name: '', ip: '', user: '', password: '', auth_type: 'password', ssh_key: '', ssh_key_passphrase: '', ssh_port: 22, color: '#3b82f6', } ); const [errors, setErrors] = useState>>({}); const [sshKeyError, setSshKeyError] = useState(''); const [colorCodingEnabled, setColorCodingEnabled] = useState(false); const [isGeneratingKey, setIsGeneratingKey] = useState(false); const [showPublicKeyModal, setShowPublicKeyModal] = useState(false); const [generatedPublicKey, setGeneratedPublicKey] = useState(''); const [, setIsGeneratedKey] = useState(false); const [, setGeneratedServerId] = useState(null); useEffect(() => { const loadColorCodingSetting = async () => { try { const response = await fetch('/api/settings/color-coding'); if (response.ok) { const data = await response.json(); setColorCodingEnabled(Boolean(data.enabled)); } } catch (error) { console.error('Error loading color coding setting:', error); } }; void loadColorCodingSetting(); }, []); const validateForm = (): boolean => { const newErrors: Partial> = {}; if (!formData.name.trim()) { newErrors.name = 'Server name is required'; } if (!formData.ip.trim()) { newErrors.ip = 'IP address is required'; } else { // Basic IP validation const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; if (!ipRegex.test(formData.ip)) { newErrors.ip = 'Please enter a valid IP address'; } } if (!formData.user.trim()) { newErrors.user = 'Username is required'; } // Validate SSH port if (formData.ssh_port !== undefined && (formData.ssh_port < 1 || formData.ssh_port > 65535)) { newErrors.ssh_port = 'SSH port must be between 1 and 65535'; } // Validate authentication based on auth_type const authType = formData.auth_type ?? 'password'; if (authType === 'password') { if (!formData.password?.trim()) { newErrors.password = 'Password is required for password authentication'; } } if (authType === 'key') { if (!formData.ssh_key?.trim()) { newErrors.ssh_key = 'SSH key is required for key authentication'; } } setErrors(newErrors); return Object.keys(newErrors).length === 0 && !sshKeyError; }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (validateForm()) { onSubmit(formData); if (!isEditing) { setFormData({ name: '', ip: '', user: '', password: '', auth_type: 'password', ssh_key: '', ssh_key_passphrase: '', ssh_port: 22, color: '#3b82f6' }); } } }; const handleChange = (field: keyof CreateServerData) => ( e: React.ChangeEvent ) => { // Special handling for numeric ssh_port: keep it strictly numeric if (field === 'ssh_port') { const raw = (e.target as HTMLInputElement).value ?? ''; const digitsOnly = raw.replace(/\D+/g, ''); setFormData(prev => ({ ...prev, ssh_port: digitsOnly ? parseInt(digitsOnly, 10) : undefined, })); if (errors.ssh_port) { setErrors(prev => ({ ...prev, ssh_port: undefined })); } return; } setFormData(prev => ({ ...prev, [field]: (e.target as HTMLInputElement).value })); // Clear error when user starts typing if (errors[field]) { setErrors(prev => ({ ...prev, [field]: undefined })); } // Reset generated key state when switching auth types if (field === 'auth_type') { setIsGeneratedKey(false); setGeneratedPublicKey(''); } }; const handleGenerateKeyPair = async () => { setIsGeneratingKey(true); try { const response = await fetch('/api/servers/generate-keypair', { method: 'POST', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) { throw new Error('Failed to generate key pair'); } const data = await response.json() as { success: boolean; privateKey?: string; publicKey?: string; serverId?: number; error?: string }; if (data.success) { const serverId = data.serverId ?? 0; const keyPath = `data/ssh-keys/server_${serverId}_key`; setFormData(prev => ({ ...prev, ssh_key: data.privateKey ?? '', ssh_key_path: keyPath, key_generated: true })); setGeneratedPublicKey(data.publicKey ?? ''); setGeneratedServerId(serverId); setIsGeneratedKey(true); setShowPublicKeyModal(true); setSshKeyError(''); } else { throw new Error(data.error ?? 'Failed to generate key pair'); } } catch (error) { console.error('Error generating key pair:', error); setSshKeyError(error instanceof Error ? error.message : 'Failed to generate key pair'); } finally { setIsGeneratingKey(false); } }; const handleSSHKeyChange = (value: string) => { setFormData(prev => ({ ...prev, ssh_key: value })); if (errors.ssh_key) { setErrors(prev => ({ ...prev, ssh_key: undefined })); } }; return ( <>
{errors.name &&

{errors.name}

}
{errors.ip &&

{errors.ip}

}
{errors.user &&

{errors.user}

}
{errors.ssh_port &&

{errors.ssh_port}

}
{colorCodingEnabled && (
Choose a color to identify this server
)}
{/* Password Authentication */} {formData.auth_type === 'password' && (
{errors.password &&

{errors.password}

}
)} {/* SSH Key Authentication */} {formData.auth_type === 'key' && (
{/* Show manual key input only if no key has been generated */} {!formData.key_generated && ( <> {errors.ssh_key &&

{errors.ssh_key}

} {sshKeyError &&

{sshKeyError}

} )} {/* Show generated key status */} {formData.key_generated && (
SSH key pair generated successfully

The private key has been generated and will be saved with the server.

)}

Only required if your SSH key is encrypted with a passphrase

)}
{isEditing && onCancel && ( )}
{/* Public Key Modal */} setShowPublicKeyModal(false)} publicKey={generatedPublicKey} serverName={formData.name || 'New Server'} serverIp={formData.ip} /> ); }