summaryrefslogtreecommitdiff
path: root/makima/frontend/src/routes/settings.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/routes/settings.tsx')
-rw-r--r--makima/frontend/src/routes/settings.tsx113
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 */}