diff options
Diffstat (limited to 'frontend/src/components/DaemonList.tsx')
| -rw-r--r-- | frontend/src/components/DaemonList.tsx | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/frontend/src/components/DaemonList.tsx b/frontend/src/components/DaemonList.tsx new file mode 100644 index 0000000..215c790 --- /dev/null +++ b/frontend/src/components/DaemonList.tsx @@ -0,0 +1,125 @@ +import React, { useEffect, useState } from 'react' +import { Link } from 'react-router-dom' + +interface DaemonSummary { + id: string + ownerId: string + connectionId: string + hostname: string | null + machineId: string | null + maxConcurrentTasks: number + currentTaskCount: number + status: string + lastHeartbeatAt: string + connectedAt: string + disconnectedAt: string | null +} + +function statusDotColor(status: string): string { + switch (status.toLowerCase()) { + case 'connected': + return 'green' + case 'disconnected': + return 'red' + case 'unhealthy': + return 'yellow' + default: + return 'gray' + } +} + +export function DaemonList() { + const [daemons, setDaemons] = useState<DaemonSummary[]>([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState<string | null>(null) + + useEffect(() => { + async function fetchDaemons() { + try { + setLoading(true) + const response = await fetch('/api/v1/mesh/daemons') + if (!response.ok) { + throw new Error(`Failed to fetch daemons: ${response.statusText}`) + } + const data = await response.json() + setDaemons(data.daemons || []) + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error') + } finally { + setLoading(false) + } + } + + fetchDaemons() + + const interval = setInterval(async () => { + try { + const response = await fetch('/api/v1/mesh/daemons') + if (response.ok) { + const data = await response.json() + setDaemons(data.daemons || []) + } + } catch { + // Silently ignore refresh errors + } + }, 10000) + + return () => clearInterval(interval) + }, []) + + if (loading) { + return ( + <div className="daemon-list-container"> + <div className="loading">Loading daemons...</div> + </div> + ) + } + + if (error) { + return ( + <div className="daemon-list-container"> + <div className="error">Error: {error}</div> + </div> + ) + } + + return ( + <div className="daemon-list-container"> + <Link to="/" className="back-link"> + Back to Home + </Link> + <h1>Daemons</h1> + {daemons.length === 0 ? ( + <p>No daemons found</p> + ) : ( + <ul className="daemon-list"> + {daemons.map((daemon) => ( + <li key={daemon.id} className="daemon-item"> + <Link to={`/daemons/${daemon.id}`}> + <h2>{daemon.hostname || 'Unknown Host'}</h2> + <div className="daemon-status"> + <span + className="status-dot" + style={{ backgroundColor: statusDotColor(daemon.status) }} + /> + <span>{daemon.status}</span> + </div> + <div className="daemon-meta"> + <span> + Tasks: {daemon.currentTaskCount} / {daemon.maxConcurrentTasks} + </span> + <span> + Connected: {new Date(daemon.connectedAt).toLocaleString()} + </span> + {daemon.machineId && ( + <span>Machine: {daemon.machineId}</span> + )} + </div> + </Link> + </li> + ))} + </ul> + )} + </div> + ) +} |
