diff options
| author | soryu <soryu@soryu.co> | 2026-01-11 05:52:14 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-15 00:21:16 +0000 |
| commit | 87044a747b47bd83249d61a45842c7f7b2eae56d (patch) | |
| tree | ef2000ce79ffcc2723ef841acef5aa1deb1d5378 /makima/frontend/src/routes/settings.tsx | |
| parent | 077820c4167c168072d217a1b01df840463a12a8 (diff) | |
| download | soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.tar.gz soryu-87044a747b47bd83249d61a45842c7f7b2eae56d.zip | |
Contract system
Diffstat (limited to 'makima/frontend/src/routes/settings.tsx')
| -rw-r--r-- | makima/frontend/src/routes/settings.tsx | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/makima/frontend/src/routes/settings.tsx b/makima/frontend/src/routes/settings.tsx index 6d56e67..7ca40ba 100644 --- a/makima/frontend/src/routes/settings.tsx +++ b/makima/frontend/src/routes/settings.tsx @@ -10,8 +10,10 @@ import { changePassword, changeEmail, deleteAccount, + listDaemons, type ApiKeyInfo, type CreateApiKeyResponse, + type Daemon, } from "../lib/api"; // ============================================================================= @@ -297,8 +299,22 @@ export default function SettingsPage() { const [deleteLoading, setDeleteLoading] = useState(false); const [deleteError, setDeleteError] = useState<string | null>(null); + // Daemon state + const [daemons, setDaemons] = useState<Daemon[]>([]); + const [daemonsLoading, setDaemonsLoading] = useState(true); + const [daemonsError, setDaemonsError] = useState<string | null>(null); + useEffect(() => { loadApiKey(); + loadDaemons(); + }, []); + + // Auto-refresh daemons every 30 seconds + useEffect(() => { + const interval = setInterval(() => { + loadDaemons(); + }, 30000); + return () => clearInterval(interval); }, []); const loadApiKey = async () => { @@ -314,6 +330,18 @@ export default function SettingsPage() { } }; + const loadDaemons = async () => { + try { + setDaemonsError(null); + const response = await listDaemons(); + setDaemons(response.daemons); + } catch (err) { + setDaemonsError(err instanceof Error ? err.message : "Failed to load daemons"); + } finally { + setDaemonsLoading(false); + } + }; + const handleCreate = async () => { try { setActionLoading(true); @@ -579,6 +607,91 @@ export default function SettingsPage() { Then run: <code className="text-green-400">makima-daemon</code> </p> </section> + + {/* Connected Daemons */} + <section className="border border-[rgba(117,170,252,0.25)] bg-[#0d1b2d] p-4"> + <div className="flex items-center justify-between mb-3 pb-2 border-b border-[rgba(117,170,252,0.15)]"> + <div className="flex items-center gap-2"> + <h2 className="text-[11px] font-mono uppercase tracking-wide text-[#8899aa]"> + Daemons + </h2> + {daemons.length > 0 && ( + <span className="text-[10px] font-mono text-[#556677]"> + ({daemons.filter(d => d.status === "connected").length} connected / {daemons.length} total) + </span> + )} + </div> + <button + onClick={loadDaemons} + disabled={daemonsLoading} + className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff] disabled:opacity-50" + title="Refresh" + > + {daemonsLoading ? "..." : "↻"} + </button> + </div> + + {daemonsError && <ErrorAlert>{daemonsError}</ErrorAlert>} + + {daemonsLoading && daemons.length === 0 ? ( + <p className="text-[#7788aa] font-mono text-xs">Loading...</p> + ) : daemons.length === 0 ? ( + <div className="text-center py-4"> + <p className="text-[#7788aa] font-mono text-xs mb-2">No daemons connected</p> + <p className="text-[#556677] font-mono text-[10px]"> + Start a daemon to enable task execution + </p> + </div> + ) : ( + <div className="space-y-2"> + {daemons.map((daemon) => ( + <div + key={daemon.id} + className="border border-[rgba(117,170,252,0.15)] bg-[#0a1525] p-3" + > + <div className="flex items-center justify-between mb-2"> + <span className="font-mono text-xs text-[#9bc3ff]"> + {daemon.hostname || "Unknown Host"} + </span> + <span + className={`text-[10px] font-mono uppercase px-2 py-0.5 border ${ + daemon.status === "connected" + ? "text-green-400 border-green-700/50 bg-green-900/20" + : daemon.status === "unhealthy" + ? "text-yellow-400 border-yellow-700/50 bg-yellow-900/20" + : "text-[#8899aa] border-[rgba(117,170,252,0.25)]" + }`} + > + {daemon.status} + </span> + </div> + <div className="font-mono text-[10px] text-[#7788aa] space-y-1"> + <div className="flex justify-between"> + <span>Tasks</span> + <span className="text-[#9bc3ff]"> + {daemon.currentTaskCount} / {daemon.maxConcurrentTasks} + </span> + </div> + <div className="flex justify-between"> + <span>Connected</span> + <span className="text-[#75aafc]"> + {new Date(daemon.connectedAt).toLocaleString()} + </span> + </div> + {daemon.machineId && ( + <div className="flex justify-between"> + <span>Machine</span> + <span className="text-[#556677] truncate ml-2" title={daemon.machineId}> + {daemon.machineId.substring(0, 16)}... + </span> + </div> + )} + </div> + </div> + ))} + </div> + )} + </section> </div> {/* Right Column */} |
