summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--makima/frontend/src/lib/api.ts36
-rw-r--r--makima/frontend/src/routes/daemons.tsx87
2 files changed, 98 insertions, 25 deletions
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index 43eaa05..458b69d 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -1222,6 +1222,42 @@ export async function restartDaemon(id: string): Promise<RestartDaemonResponse>
}
// =============================================================================
+// Daemon Platform Download
+// =============================================================================
+
+/** A daemon platform with its availability and download URL */
+export interface DaemonPlatform {
+ platform: string;
+ available: boolean;
+ downloadUrl: string;
+}
+
+/** Response from the list daemon platforms endpoint */
+export interface DaemonPlatformsResponse {
+ platforms: DaemonPlatform[];
+}
+
+/**
+ * List available daemon platforms and their download status.
+ * This is an unauthenticated endpoint.
+ */
+export async function listDaemonPlatforms(): Promise<DaemonPlatformsResponse> {
+ const res = await fetch(`${API_BASE}/api/v1/daemon/download/platforms`);
+ if (!res.ok) {
+ throw new Error(`Failed to list daemon platforms: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/**
+ * Get the full download URL for a daemon binary.
+ * Returns the absolute URL including API_BASE for cross-origin usage.
+ */
+export function getDaemonDownloadUrl(platform: string): string {
+ return `${API_BASE}/api/v1/daemon/download/${platform}`;
+}
+
+// =============================================================================
// Mesh Chat Types for Task Orchestration
// =============================================================================
diff --git a/makima/frontend/src/routes/daemons.tsx b/makima/frontend/src/routes/daemons.tsx
index 5f078f8..f551543 100644
--- a/makima/frontend/src/routes/daemons.tsx
+++ b/makima/frontend/src/routes/daemons.tsx
@@ -1,12 +1,15 @@
-import { useState, useEffect } from "react";
+import { useState, useEffect, useCallback } from "react";
import { useAuth } from "../contexts/AuthContext";
import { useNavigate } from "react-router";
import { Masthead } from "../components/Masthead";
import {
listDaemons,
restartDaemon,
+ listDaemonPlatforms,
+ API_BASE,
type Daemon,
type DaemonListResponse,
+ type DaemonPlatform,
type RestartDaemonResponse,
} from "../lib/api";
@@ -49,6 +52,10 @@ export default function DaemonsPage() {
const [restartingDaemonId, setRestartingDaemonId] = useState<string | null>(null);
const [restartConfirmDaemonId, setRestartConfirmDaemonId] = useState<string | null>(null);
+ // Platform availability state
+ const [platforms, setPlatforms] = useState<DaemonPlatform[]>([]);
+ const [platformsLoading, setPlatformsLoading] = useState(true);
+
// Redirect if not authenticated
useEffect(() => {
if (isAuthConfigured && !isAuthenticated) {
@@ -85,9 +92,36 @@ export default function DaemonsPage() {
}
};
+ // Friendly labels for platform identifiers
+ const platformLabels: Record<string, string> = {
+ "linux-x86_64": "Linux (Intel/AMD)",
+ "linux-arm64": "Linux (ARM64)",
+ "macos-x86_64": "macOS (Intel)",
+ "macos-arm64": "macOS (Apple Silicon)",
+ };
+
+ const loadPlatforms = useCallback(async () => {
+ try {
+ setPlatformsLoading(true);
+ const response = await listDaemonPlatforms();
+ setPlatforms(response.platforms);
+ } catch {
+ // Fallback: show all platforms as unavailable if API endpoint is missing
+ setPlatforms([
+ { platform: "linux-x86_64", available: false, downloadUrl: "/api/v1/daemon/download/linux-x86_64" },
+ { platform: "linux-arm64", available: false, downloadUrl: "/api/v1/daemon/download/linux-arm64" },
+ { platform: "macos-x86_64", available: false, downloadUrl: "/api/v1/daemon/download/macos-x86_64" },
+ { platform: "macos-arm64", available: false, downloadUrl: "/api/v1/daemon/download/macos-arm64" },
+ ]);
+ } finally {
+ setPlatformsLoading(false);
+ }
+ }, []);
+
// Initial load
useEffect(() => {
loadDaemons();
+ loadPlatforms();
}, []);
// Auto-refresh daemons every 30 seconds
@@ -123,30 +157,33 @@ export default function DaemonsPage() {
</p>
<div className="grid grid-cols-2 gap-2 mb-4">
- <a
- href="/api/v1/daemon/download/linux-x86_64"
- className="flex items-center justify-center gap-2 px-3 py-2 border border-[rgba(117,170,252,0.25)] bg-[#0a1525] text-[#75aafc] hover:text-[#9bc3ff] hover:border-[#3f6fb3] transition-colors font-mono text-[10px] uppercase tracking-wide"
- >
- <span>Linux x86_64</span>
- </a>
- <a
- href="/api/v1/daemon/download/linux-arm64"
- className="flex items-center justify-center gap-2 px-3 py-2 border border-[rgba(117,170,252,0.25)] bg-[#0a1525] text-[#75aafc] hover:text-[#9bc3ff] hover:border-[#3f6fb3] transition-colors font-mono text-[10px] uppercase tracking-wide"
- >
- <span>Linux ARM64</span>
- </a>
- <a
- href="/api/v1/daemon/download/macos-x86_64"
- className="flex items-center justify-center gap-2 px-3 py-2 border border-[rgba(117,170,252,0.25)] bg-[#0a1525] text-[#75aafc] hover:text-[#9bc3ff] hover:border-[#3f6fb3] transition-colors font-mono text-[10px] uppercase tracking-wide"
- >
- <span>macOS Intel</span>
- </a>
- <a
- href="/api/v1/daemon/download/macos-arm64"
- className="flex items-center justify-center gap-2 px-3 py-2 border border-[rgba(117,170,252,0.25)] bg-[#0a1525] text-[#75aafc] hover:text-[#9bc3ff] hover:border-[#3f6fb3] transition-colors font-mono text-[10px] uppercase tracking-wide"
- >
- <span>macOS Apple Silicon</span>
- </a>
+ {platformsLoading ? (
+ <p className="col-span-2 text-[#7788aa] font-mono text-[10px]">Loading platforms...</p>
+ ) : (
+ platforms.map((p) => (
+ <a
+ key={p.platform}
+ href={p.available ? `${API_BASE}${p.downloadUrl}` : undefined}
+ download={p.available ? true : undefined}
+ className={`flex flex-col items-center justify-center gap-1 p-3 border border-[rgba(117,170,252,0.25)] bg-[#0a1525] font-mono text-xs text-[#9bc3ff] transition-colors ${
+ p.available
+ ? "hover:bg-[#0d1f3a] cursor-pointer"
+ : "opacity-50 pointer-events-none"
+ }`}
+ >
+ <span className="text-[10px] uppercase tracking-wide">
+ {platformLabels[p.platform] || p.platform}
+ </span>
+ <span
+ className={`text-[10px] ${
+ p.available ? "text-green-400" : "text-[#556677]"
+ }`}
+ >
+ {p.available ? "Available" : "Not bundled"}
+ </span>
+ </a>
+ ))
+ )}
</div>
<div className="border-t border-[rgba(117,170,252,0.15)] pt-3">