import { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import type { User, DroneRegistration } from '../lib/api'; import { droneApi } from '../lib/api'; interface DroneManagerProps { user: User | null; } interface LogEntry { id: number; timestamp: string; message: string; } export default function DroneManager({ user }: DroneManagerProps) { const navigate = useNavigate(); const [allDrones, setAllDrones] = useState([]); const [selectedDrone, setSelectedDrone] = useState(null); const [loading, setLoading] = useState(true); const [terminating, setTerminating] = useState(null); const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' } | null>(null); // Placeholder log entries for testing scroll behavior const [logEntries, setLogEntries] = useState([]); const logContainerRef = useRef(null); const [autoScroll, setAutoScroll] = useState(true); useEffect(() => { if (!user) { navigate('/sign-in'); return; } loadDrones(); }, [user]); // Mock log generator for testing scroll behavior // TODO: Replace with live log streaming when backend is implemented useEffect(() => { if (!selectedDrone) { setLogEntries([]); return; } // Initialize with some placeholder entries const initialEntries: LogEntry[] = Array.from({ length: 50 }, (_, i) => ({ id: i, timestamp: new Date(Date.now() - (50 - i) * 1000).toLocaleTimeString(), message: `[PLACEHOLDER] Log entry ${i + 1} - This is mock data for testing scroll behavior`, })); setLogEntries(initialEntries); // Add a new entry every second for testing auto-scroll const interval = setInterval(() => { setLogEntries(prev => { const newEntry: LogEntry = { id: prev.length > 0 ? prev[prev.length - 1].id + 1 : 0, timestamp: new Date().toLocaleTimeString(), message: `[PLACEHOLDER] New log entry at ${new Date().toLocaleTimeString()} - Auto-generated for testing`, }; const updated = [...prev, newEntry]; // Keep only last 200 entries as per spec if (updated.length > 200) { return updated.slice(updated.length - 200); } return updated; }); }, 1000); return () => clearInterval(interval); }, [selectedDrone]); // Auto-scroll log to bottom when new entries arrive (if user hasn't scrolled up) useEffect(() => { if (autoScroll && logContainerRef.current) { logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; } }, [logEntries, autoScroll]); const handleScroll = () => { if (logContainerRef.current) { const { scrollTop, scrollHeight, clientHeight } = logContainerRef.current; // Check if user is near the bottom (within 100px) const isNearBottom = scrollHeight - scrollTop - clientHeight < 100; setAutoScroll(isNearBottom); } }; const loadDrones = async () => { try { const data = await droneApi.getForManager(); setAllDrones(data); } catch (err) { console.error('Failed to load drones', err); showToast('Failed to load drones', 'error'); } finally { setLoading(false); } }; const showToast = (message: string, type: 'success' | 'error') => { setToast({ message, type }); setTimeout(() => setToast(null), 5000); }; const handleSelectDrone = (drone: DroneRegistration) => { setSelectedDrone(drone); setLogEntries([]); // Clear log when switching drones }; const handleTerminate = async () => { if (!selectedDrone) return; // Show confirmation if drone is busy if (selectedDrone.status === 'busy') { const confirmed = confirm('Are you sure you want to terminate the drone? Any work or operations currently in progress may be lost.'); if (!confirmed) return; } setTerminating(selectedDrone._id); try { const result = await droneApi.terminate(selectedDrone._id); if (result.message) { showToast(result.message, result.success ? 'success' : 'error'); } else { showToast('Drone terminated successfully', 'success'); } // Refresh drone list await loadDrones(); // Clear selection if drone is now offline if (selectedDrone.status !== 'offline') { setSelectedDrone(null); } } catch (err) { console.error('Failed to terminate drone', err); showToast('Failed to terminate drone', 'error'); } finally { setTerminating(null); } }; const onlineDrones = allDrones.filter(d => d.status === 'available' || d.status === 'busy'); const offlineDrones = allDrones.filter(d => d.status === 'offline' || d.status === 'starting'); if (!user) { return (

Please sign in to view drones.

); } return (
{/* Toast Notification */} {toast && (
{toast.message}
)} {/* Left Panel - Drone Lists */} {/* Right Panel - Drone Details */}
{selectedDrone ? ( <> {/* Drone Info Card */}

Drone Details

{selectedDrone.status !== 'offline' && ( )}
Hostname
{selectedDrone.hostname}
Status
{selectedDrone.status}
Workspace
{selectedDrone.workspaceDir}
Registered
{new Date(selectedDrone.createdAt).toLocaleString()}
{/* Drone Monitor Placeholder */}

Drone Monitor

Monitor charts coming soon.

Memory usage, AI operations, and log production metrics will be displayed here.

{/* Log Viewer - Collapsible panel at bottom */}

Drone Log {selectedDrone.status !== 'offline' && '(Live)'}

{logEntries.length === 0 ? (

No log entries yet.

{/* TODO: Remove this placeholder comment when live logs are implemented */} [PLACEHOLDER] Log entries will appear here when the drone is running.

) : ( logEntries.map((entry) => (
[{entry.timestamp}] {entry.message}
)) )}
) : (

Select a drone to view details

Choose from the online or offline drone lists

)}
); }