From 0523765af84492640928d571f481e17b26008b13 Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Sat, 21 Feb 2026 23:51:11 +0000
Subject: feat: Add daemon health monitoring page, downloads & K8s support
 (#76)

* feat: soryu-co/soryu - makima: Add server-side daemon binary download endpoint

* feat: soryu-co/soryu - makima: Create Kubernetes daemon manifests and Dockerfile

* feat: soryu-co/soryu - makima: Create dedicated Daemons page with health monitoring UI

* WIP: heartbeat checkpoint

* feat: soryu-co/soryu - makima: Integrate daemon platform availability into frontend downloads
---
 makima/frontend/src/routes/settings.tsx | 185 ++------------------------------
 1 file changed, 6 insertions(+), 179 deletions(-)

(limited to 'makima/frontend/src/routes/settings.tsx')

diff --git a/makima/frontend/src/routes/settings.tsx b/makima/frontend/src/routes/settings.tsx
index b93ecbc..73537bd 100644
--- a/makima/frontend/src/routes/settings.tsx
+++ b/makima/frontend/src/routes/settings.tsx
@@ -10,13 +10,10 @@ import {
   changePassword,
   changeEmail,
   deleteAccount,
-  listDaemons,
-  restartDaemon,
   listRepositoryHistory,
   deleteRepositoryHistory,
   type ApiKeyInfo,
   type CreateApiKeyResponse,
-  type Daemon,
   type RepositoryHistoryEntry,
 } from "../lib/api";
 
@@ -303,13 +300,6 @@ 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);
-  const [restartingDaemonId, setRestartingDaemonId] = useState<string | null>(null);
-  const [restartConfirmDaemonId, setRestartConfirmDaemonId] = useState<string | null>(null);
-
   // Repository history state
   const [repoHistory, setRepoHistory] = useState<RepositoryHistoryEntry[]>([]);
   const [repoHistoryLoading, setRepoHistoryLoading] = useState(true);
@@ -318,18 +308,9 @@ export default function SettingsPage() {
 
   useEffect(() => {
     loadApiKey();
-    loadDaemons();
     loadRepoHistory();
   }, []);
 
-  // Auto-refresh daemons every 30 seconds
-  useEffect(() => {
-    const interval = setInterval(() => {
-      loadDaemons();
-    }, 30000);
-    return () => clearInterval(interval);
-  }, []);
-
   const loadApiKey = async () => {
     try {
       setLoading(true);
@@ -343,18 +324,6 @@ 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 loadRepoHistory = async () => {
     try {
       setRepoHistoryError(null);
@@ -379,23 +348,6 @@ export default function SettingsPage() {
     }
   };
 
-  const handleRestartDaemon = async (id: string) => {
-    try {
-      setRestartingDaemonId(id);
-      setDaemonsError(null);
-      await restartDaemon(id);
-      // Daemon will restart, so refresh the list after a short delay
-      setTimeout(() => {
-        loadDaemons();
-      }, 2000);
-    } catch (err) {
-      setDaemonsError(err instanceof Error ? err.message : "Failed to restart daemon");
-    } finally {
-      setRestartingDaemonId(null);
-      setRestartConfirmDaemonId(null);
-    }
-  };
-
   const handleCreate = async () => {
     try {
       setActionLoading(true);
@@ -648,140 +600,15 @@ export default function SettingsPage() {
               )}
             </section>
 
-            {/* Daemon Setup */}
+            {/* Daemons Link */}
             <section className="border border-[rgba(117,170,252,0.25)] bg-[#0d1b2d] p-4">
-              <SectionHeader>Daemon Setup</SectionHeader>
+              <SectionHeader>Daemons</SectionHeader>
               <p className="text-[#7788aa] font-mono text-[10px] mb-3">
-                Set your API key as an environment variable:
-              </p>
-              <code className="block bg-black/50 px-3 py-2 text-[11px] font-mono text-green-400 mb-3">
-                export MAKIMA_API_KEY="your-key"
-              </code>
-              <p className="text-[#7788aa] font-mono text-[10px]">
-                Then run: <code className="text-green-400">makima-daemon</code>
+                Daemon management has moved to its own page.
               </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>
-                        <div className="flex items-center gap-2">
-                          <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>
-                      <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>
-                      {/* Restart Section */}
-                      {daemon.status === "connected" && (
-                        <div className="mt-3 pt-2 border-t border-[rgba(117,170,252,0.1)]">
-                          {restartConfirmDaemonId === daemon.id ? (
-                            <div className="flex items-center justify-between gap-2">
-                              <span className="text-[10px] font-mono text-yellow-400">
-                                Restart daemon? Running tasks will be interrupted.
-                              </span>
-                              <div className="flex gap-2">
-                                <button
-                                  onClick={() => setRestartConfirmDaemonId(null)}
-                                  className="text-[10px] font-mono text-[#7788aa] hover:text-[#9bc3ff] px-2 py-1"
-                                  disabled={restartingDaemonId === daemon.id}
-                                >
-                                  Cancel
-                                </button>
-                                <button
-                                  onClick={() => handleRestartDaemon(daemon.id)}
-                                  disabled={restartingDaemonId === daemon.id}
-                                  className="text-[10px] font-mono text-red-400 hover:text-red-300 px-2 py-1 border border-red-700/50 bg-red-900/20 disabled:opacity-50"
-                                >
-                                  {restartingDaemonId === daemon.id ? "Restarting..." : "Confirm"}
-                                </button>
-                              </div>
-                            </div>
-                          ) : (
-                            <button
-                              onClick={() => setRestartConfirmDaemonId(daemon.id)}
-                              className="text-[10px] font-mono text-[#75aafc] hover:text-[#9bc3ff]"
-                            >
-                              ⟳ Restart Daemon
-                            </button>
-                          )}
-                        </div>
-                      )}
-                    </div>
-                  ))}
-                </div>
-              )}
+              <a href="/daemons" className="text-[#75aafc] hover:text-[#9bc3ff] text-xs font-mono">
+                Go to Daemons &rarr;
+              </a>
             </section>
 
             {/* Repository History */}
-- 
cgit v1.2.3

