'use client'; import { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { Progress } from '@/components/ui/progress'; import { CheckCircle2, XCircle, Loader2, ArrowLeft, ArrowRight, AlertCircle, ExternalLink, Download, Settings, Play, Square, X } from 'lucide-react'; import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; import { cn } from '@/lib/utils'; interface SearxngInstallWizardProps { onComplete: () => void; onCancel: () => void; initialUrl?: string; } type Step = 0 | 1 | 2 | 3 | 4 | 5; interface SystemRequirements { status: 'pass' | 'fail' | 'warn'; os: string; arch: string; ram_mb: number; disk_gb: number; cpu_cores: number; kernel_version?: string; issues: string[]; warnings: string[]; } interface InstallProgressEvent { event_type: 'step_start' | 'log' | 'step_complete' | 'error' | 'complete'; step?: number; step_name?: string; total_steps?: number; progress: number; message?: string; log_line?: string; } interface ValidationResult { status: 'pass' | 'fail' | 'partial'; tests: Array<{ name: string; status: 'pass' | 'fail' | 'warn'; message?: string; }>; docker_version?: string; } export function SearxngInstallWizard({ onComplete, onCancel, initialUrl = 'http://localhost:8080' }: SearxngInstallWizardProps) { const [currentStep, setCurrentStep] = useState(0); const [requirements, setRequirements] = useState(null); const [isCheckingRequirements, setIsCheckingRequirements] = useState(false); const [dockerAvailable, setDockerAvailable] = useState(null); const [dockerRunning, setDockerRunning] = useState(null); const [isChecking, setIsChecking] = useState(false); const [url, setUrl] = useState(initialUrl); const [port, setPort] = useState('8080'); const [autoStart, setAutoStart] = useState(true); const [installProgress, setInstallProgress] = useState(''); const [isInstalling, setIsInstalling] = useState(false); const [installError, setInstallError] = useState(null); const [dockerInstallProgress, setDockerInstallProgress] = useState<{ currentStep: number; totalSteps: number; progress: number; currentStepName: string; logs: string[]; }>({ currentStep: 0, totalSteps: 0, progress: 0, currentStepName: '', logs: [], }); const [isInstallingDocker, setIsInstallingDocker] = useState(false); const [validationResult, setValidationResult] = useState(null); const [isValidating, setIsValidating] = useState(false); const [verificationStatus, setVerificationStatus] = useState<'checking' | 'success' | 'error' | null>(null); const [verificationError, setVerificationError] = useState(null); // Step 0: Verificar Requisitos useEffect(() => { if (currentStep === 0) { checkRequirements(); } }, [currentStep]); // Step 1: Verificar Docker useEffect(() => { if (currentStep === 1) { checkDocker(); } }, [currentStep]); // Listener de eventos de progresso Docker useEffect(() => { let unlistenPromise: Promise<() => void> | null = null; if (isInstallingDocker) { unlistenPromise = listen('docker-install-progress', (event) => { const data = event.payload; switch (data.event_type) { case 'step_start': setDockerInstallProgress(prev => ({ ...prev, currentStep: data.step || 0, totalSteps: data.total_steps || prev.totalSteps, currentStepName: data.step_name || '', progress: data.progress, })); if (data.message) { setDockerInstallProgress(prev => ({ ...prev, logs: [...prev.logs.slice(-49), data.message!], })); } break; case 'log': if (data.log_line) { setDockerInstallProgress(prev => ({ ...prev, logs: [...prev.logs.slice(-49), data.log_line!], })); } break; case 'step_complete': setDockerInstallProgress(prev => ({ ...prev, progress: data.progress, })); if (data.message) { setDockerInstallProgress(prev => ({ ...prev, logs: [...prev.logs.slice(-49), data.message!], })); } break; case 'error': setInstallError(data.message || 'Erro desconhecido'); setIsInstallingDocker(false); break; case 'complete': setDockerInstallProgress(prev => ({ ...prev, progress: 100 })); setIsInstallingDocker(false); // Validar instalação após conclusão setTimeout(() => { validateDocker(); }, 1000); break; } }); } return () => { if (unlistenPromise) { unlistenPromise.then(fn => fn()); } }; }, [isInstallingDocker]); const checkRequirements = async () => { setIsCheckingRequirements(true); try { const reqs = await invoke('check_docker_requirements'); setRequirements(reqs); } catch (error) { console.error('Error checking requirements:', error); setRequirements({ status: 'fail', os: 'unknown', arch: 'unknown', ram_mb: 0, disk_gb: 0, cpu_cores: 0, issues: ['Erro ao verificar requisitos do sistema'], warnings: [], }); } finally { setIsCheckingRequirements(false); } }; const checkDocker = async () => { setIsChecking(true); try { const available = await invoke('check_docker_available'); setDockerAvailable(available); if (available) { const running = await invoke('check_docker_running'); setDockerRunning(running); } } catch (error) { console.error('Error checking Docker:', error); setDockerAvailable(false); setDockerRunning(false); } finally { setIsChecking(false); } }; const handleNext = () => { if (currentStep < 5) { setCurrentStep((prev) => (prev + 1) as Step); } }; const handleBack = () => { if (currentStep > 0) { setCurrentStep((prev) => (prev - 1) as Step); } }; const handleInstallDocker = async () => { setIsInstallingDocker(true); setInstallError(null); setDockerInstallProgress({ currentStep: 0, totalSteps: 0, progress: 0, currentStepName: '', logs: [], }); try { await invoke('install_docker'); } catch (error) { console.error('Docker installation error:', error); setInstallError(error instanceof Error ? error.message : 'Erro desconhecido durante instalação do Docker'); setIsInstallingDocker(false); } }; const validateDocker = async () => { setIsValidating(true); setValidationResult(null); try { const result = await invoke('validate_docker_installation'); setValidationResult(result); if (result.status === 'pass' || result.status === 'partial') { // Docker validado, atualizar estado setDockerAvailable(true); setDockerRunning(true); } } catch (error) { console.error('Validation error:', error); setValidationResult({ status: 'fail', tests: [{ name: 'Validação', status: 'fail', message: error instanceof Error ? error.message : 'Erro ao validar Docker', }], }); } finally { setIsValidating(false); } }; const handleInstall = async () => { setIsInstalling(true); setInstallError(null); setInstallProgress('Iniciando instalação...'); try { setInstallProgress('Baixando imagem SearXNG...'); await invoke('install_searxng', { url, port: parseInt(port, 10) }); setInstallProgress('Criando container...'); // Installation is handled by install_searxng command setInstallProgress('Iniciando serviços...'); await invoke('start_searxng'); setInstallProgress('Instalação concluída!'); setTimeout(() => { handleNext(); }, 1000); } catch (error) { console.error('Installation error:', error); setInstallError(error instanceof Error ? error.message : 'Erro desconhecido durante instalação'); } finally { setIsInstalling(false); } }; const handleVerify = async () => { setVerificationStatus('checking'); setVerificationError(null); try { const isAvailable = await invoke('check_searxng_status', { url }); if (isAvailable) { setVerificationStatus('success'); setTimeout(() => { onComplete(); }, 1500); } else { setVerificationStatus('error'); setVerificationError('SearXNG não está respondendo. Verifique se o container está rodando.'); } } catch (error) { setVerificationStatus('error'); setVerificationError(error instanceof Error ? error.message : 'Erro ao verificar SearXNG'); } }; const renderStep = () => { switch (currentStep) { case 0: return (

Verificação de Requisitos do Sistema

Verificando se seu sistema atende aos requisitos para instalação do Docker.

{isCheckingRequirements ? (
Verificando requisitos...
) : requirements ? (
Status Geral {requirements.status === 'pass' ? ( Compatível ) : requirements.status === 'warn' ? ( Avisos ) : ( Incompatível )}

Sistema Operacional

{requirements.os} ({requirements.arch})

RAM

{(requirements.ram_mb / 1024).toFixed(1)} GB

Disco Livre

{requirements.disk_gb} GB

CPU Cores

{requirements.cpu_cores}

{requirements.kernel_version && (

Versão do Kernel

{requirements.kernel_version}

)}
{requirements.issues.length > 0 && (

Problemas Encontrados:

    {requirements.issues.map((issue, idx) => (
  • {issue}
  • ))}
)} {requirements.warnings.length > 0 && (

Avisos:

    {requirements.warnings.map((warning, idx) => (
  • {warning}
  • ))}
)}
) : null}
); case 1: return (

Verificação de Docker

SearXNG requer Docker para funcionar. Vamos verificar se está instalado e rodando.

{isChecking ? (
Verificando Docker...
) : (
Docker Instalado {dockerAvailable === null ? ( ) : dockerAvailable ? ( ) : ( )}
{dockerAvailable === false && (

Docker não está instalado. Você precisa instalá-lo primeiro.

)}
{dockerAvailable && (
Docker Rodando {dockerRunning === null ? ( ) : dockerRunning ? ( ) : ( )}
{dockerRunning === false && (

Docker está instalado mas não está rodando. Inicie o Docker Desktop.

)}
)} {dockerAvailable === false && (

Docker não está instalado. Você pode instalá-lo agora ou manualmente.

)} {isInstallingDocker && (
{dockerInstallProgress.currentStepName || 'Instalando Docker...'} {dockerInstallProgress.progress}%
{dockerInstallProgress.logs.length > 0 && (
{dockerInstallProgress.logs.map((log, idx) => (
{log}
))}
)}
)} {validationResult && (

Resultados da Validação:

{validationResult.tests.map((test, idx) => (
{test.name} {test.status === 'pass' ? ( ) : test.status === 'warn' ? ( ) : ( )}
))} {validationResult.docker_version && (

Docker {validationResult.docker_version} instalado

)}
)}
{validationResult && (validationResult.status === 'pass' || validationResult.status === 'partial') && ( )} {!validationResult && dockerAvailable && dockerRunning && ( )} {!dockerAvailable && !isInstallingDocker && ( )}
)}
); case 2: return (

Configuração

Configure a URL e porta onde SearXNG será acessível.

setUrl(e.target.value)} placeholder="http://localhost:8080" />

URL completa onde o SearXNG estará acessível

setPort(e.target.value)} placeholder="8080" min="1024" max="65535" />

Porta que será usada pelo container (padrão: 8080)

setAutoStart(checked === true)} />
); case 3: return (

Instalação do SearXNG

Instalando e configurando SearXNG. Isso pode levar alguns minutos.

{isInstalling ? (

{installProgress}

) : installError ? (

Erro durante instalação

{installError}

) : (

Instalação concluída!

SearXNG foi instalado com sucesso.

)}
{!isInstalling && !installError && ( )} {isInstalling && ( )}
); case 5: return (

Verificação

Verificando se SearXNG está funcionando corretamente.

{verificationStatus === null && (

Clique em "Verificar" para testar a conexão com SearXNG.

)} {verificationStatus === 'checking' && (
Verificando conexão...
)} {verificationStatus === 'success' && (

SearXNG está disponível!

A conexão foi estabelecida com sucesso.

)} {verificationStatus === 'error' && (

Falha na verificação

{verificationError || 'SearXNG não está respondendo'}

)}
{verificationStatus === 'success' && ( )}
); default: return null; } }; return (
SearXNG - Instalação Passo {currentStep + 1} de 6 {renderStep()}
); }