import React, { useEffect, useState } from 'react'; import api from '../utils/api'; import './Dashboard.css'; // Re-use common dashboard styles import { Server, Cpu, Globe, Database, Clock, RefreshCw, Upload } from 'lucide-react'; interface Decky { name: string; ip: string; services: string[]; distro: string; hostname: string; archetype: string | null; service_config: Record>; mutate_interval: number | null; last_mutated: number; } const DeckyFleet: React.FC = () => { const [deckies, setDeckies] = useState([]); const [loading, setLoading] = useState(true); const [mutating, setMutating] = useState(null); const [showDeploy, setShowDeploy] = useState(false); const [iniContent, setIniContent] = useState(''); const [deploying, setDeploying] = useState(false); const fetchDeckies = async () => { try { const _res = await api.get('/deckies'); setDeckies(_res.data); } catch (err) { console.error('Failed to fetch decky fleet', err); } finally { setLoading(false); } }; const handleMutate = async (name: string) => { setMutating(name); try { await api.post(`/deckies/${name}/mutate`, {}, { timeout: 120000 }); await fetchDeckies(); } catch (err: any) { console.error('Failed to mutate', err); if (err.code === 'ECONNABORTED') { alert('Mutation is still running in the background but the UI timed out.'); } else { alert('Mutation failed'); } } finally { setMutating(null); } }; const handleIntervalChange = async (name: string, current: number | null) => { const _val = prompt(`Enter new mutation interval in minutes for ${name} (leave empty to disable):`, current?.toString() || ''); if (_val === null) return; const mutate_interval = _val.trim() === '' ? null : parseInt(_val); try { await api.put(`/deckies/${name}/mutate-interval`, { mutate_interval }); fetchDeckies(); } catch (err) { console.error('Failed to update interval', err); alert('Update failed'); } }; const handleDeploy = async () => { if (!iniContent.trim()) return; setDeploying(true); try { await api.post('/deckies/deploy', { ini_content: iniContent }, { timeout: 120000 }); setIniContent(''); setShowDeploy(false); fetchDeckies(); } catch (err: any) { console.error('Deploy failed', err); alert(`Deploy failed: ${err.response?.data?.detail || err.message}`); } finally { setDeploying(false); } }; const handleFileUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { const content = event.target?.result as string; setIniContent(content); }; reader.readAsText(file); }; useEffect(() => { fetchDeckies(); const _interval = setInterval(fetchDeckies, 10000); // Fleet state updates less frequently than logs return () => clearInterval(_interval); }, []); if (loading) return
SCANNING NETWORK FOR DECOYS...
; return (

DECOY FLEET ASSET INVENTORY

{showDeploy && (

Deploy via INI Configuration