import { useEffect, useRef, useState } from "react"; import { RefreshCw, HardDrive, Cpu, Plug, Gauge } from "lucide-react"; import { VolumeWizard } from "@/components/volumes/VolumeWizard"; import { GPUSelector } from "@/components/gpu/GPUSelector"; import { DevicePanel } from "@/components/gpu/DevicePanel"; import { ResourcePanel } from "@/components/stacks/ResourcePanel"; import { editorApi } from "@/api/editor"; import { apiErrorMessage } from "@/api/client"; import { toast } from "sonner"; type Tab = "volumes" | "gpu" | "devices" | "resources"; export function EditorHelperPanel({ yaml, onYaml, }: { yaml: string; onYaml: (next: string) => void; }) { const [services, setServices] = useState([]); const [service, setService] = useState(""); const [tab, setTab] = useState("volumes"); const [privileged, setPrivileged] = useState(false); // Only the newest request may answer: an older one finishing late would put // back the services of a YAML that has since changed. const request = useRef(0); const loadServices = async () => { const mine = ++request.current; try { const svc = await editorApi.services(yaml); if (mine !== request.current) return; setServices(svc); setService((cur) => (cur && svc.includes(cur) ? cur : svc[0] ?? "")); } catch (e) { /* invalid yaml while typing — ignore */ } }; // Follows the YAML (debounced, it is a server round trip). Read once on mount // it listed the editor's starter service "app" for every existing stack — // its file only arrives after the panel mounted — and the helpers then // wrote into a service that did not exist. useEffect(() => { const t = setTimeout(loadServices, 500); return () => clearTimeout(t); // eslint-disable-next-line react-hooks/exhaustive-deps }, [yaml]); const guard = (): string | null => { if (!service) { toast.error("Add/select a service first (check YAML is valid, then ↻)"); return null; } return service; }; const run = async (fn: () => Promise) => { try { onYaml(await fn()); toast.success("Applied to YAML"); } catch (e) { toast.error(apiErrorMessage(e)); } }; return (
{/* Service picker */}
Service
{/* Tabs */}
{([ ["volumes", "Volumes", HardDrive], ["gpu", "GPU", Cpu], ["devices", "Devices", Plug], ["resources", "Limits", Gauge], ] as [Tab, string, typeof Cpu][]).map(([id, label, Icon]) => ( ))}
{tab === "volumes" && ( { if (!guard()) return; run(() => editorApi.addVolume(yaml, service, spec)); }} /> )} {tab === "gpu" && ( { if (!guard()) return; run(() => editorApi.setGpu(yaml, service, config)); }} /> )} {tab === "devices" && ( { if (!guard()) return; run(() => editorApi.addDevice(yaml, service, path)); }} onTogglePrivileged={(value) => { if (!guard()) return; setPrivileged(value); run(() => editorApi.setPrivileged(yaml, service, value)); }} /> )} {tab === "resources" && ( { if (!guard()) return; run(() => editorApi.setResources(yaml, service, res)); }} /> )}
); }