import React, { useState, useEffect, useRef } from 'react'; const AppDetail = ({ app, onBack }) => { const [formData, setFormData] = useState({ domain: '', dbPassword: '', port: app.vhost?.proxyPort || '' }); const [installing, setInstalling] = useState(false); const [logs, setLogs] = useState([]); const [status, setStatus] = useState(null); // 'success' | 'error' const logEndRef = useRef(null); const scrollToBottom = () => { logEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { scrollToBottom(); }, [logs]); const handleInstall = async (e) => { e.preventDefault(); setInstalling(true); setLogs([]); setStatus(null); try { // Use fetch with stream reader to handle POST with SSE-like response const response = await fetch(`/api/v1/apps/${app.id}/install`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { value, done } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); lines.forEach(line => { if (line.trim().startsWith('data: ')) { try { const data = JSON.parse(line.trim().slice(6)); if (data.log) { setLogs(prev => [...prev, data.log]); } if (data.completed) { setStatus(data.success ? 'success' : 'error'); if (data.error) setLogs(prev => [...prev, `ERROR: ${data.error}`]); } } catch (e) { console.error('Error parsing SSE line', e); } } }); } } catch (err) { setLogs(prev => [...prev, `FATAL: ${err.message}`]); setStatus('error'); } finally { setInstalling(false); } }; return (
{app.description}
Deployed to {app.installedInfo?.domain} on {new Date(app.installedInfo?.installedAt).toLocaleDateString()}