summaryrefslogtreecommitdiff
path: root/frontend/src/components/DaemonList.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/DaemonList.tsx')
-rw-r--r--frontend/src/components/DaemonList.tsx125
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>
+ )
+}