Remote deploy console: stream agent compose up to the browser (0.23.0)
Extends the live deploy console to remote/agent stacks. New agent WS endpoint
`/agent/ws/deploy/{stack_id}` runs `compose up -d` and streams its output; the
central app proxies it through `/ws/agent-deploy/{agent_id}/{stack_id}` (same
pattern + token URL-encoding as the agent-logs proxy) and records an
`agent.stack.start` audit entry. The editor's remote Deploy path now opens the
DeployConsole (agentId) instead of the blocking `agentsApi.action(start)`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7592085ce9
commit
d46a6c3576
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "stackpilot-frontend",
|
||||
"private": true,
|
||||
"version": "0.22.0",
|
||||
"version": "0.23.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -15,9 +15,11 @@ type Phase = "running" | "success" | "failed" | "error";
|
||||
*/
|
||||
export function DeployConsole({
|
||||
stackId,
|
||||
agentId,
|
||||
onClose,
|
||||
}: {
|
||||
stackId: string;
|
||||
agentId?: number;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const [lines, setLines] = useState<string[]>([]);
|
||||
@@ -29,7 +31,11 @@ export function DeployConsole({
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
const proto = window.location.protocol === "https:" ? "wss" : "ws";
|
||||
const url = `${proto}://${window.location.host}/ws/deploy/${stackId}?token=${token}`;
|
||||
const path =
|
||||
agentId != null
|
||||
? `/ws/agent-deploy/${agentId}/${stackId}`
|
||||
: `/ws/deploy/${stackId}`;
|
||||
const url = `${proto}://${window.location.host}${path}?token=${token}`;
|
||||
const ws = new WebSocket(url);
|
||||
ws.onmessage = (ev) => {
|
||||
try {
|
||||
@@ -54,7 +60,7 @@ export function DeployConsole({
|
||||
setPhase((p) => (p === "running" ? "error" : p));
|
||||
};
|
||||
return () => ws.close();
|
||||
}, [stackId, token]);
|
||||
}, [stackId, agentId, token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (boxRef.current) boxRef.current.scrollTop = boxRef.current.scrollHeight;
|
||||
|
||||
@@ -42,6 +42,7 @@ export function StackEditor() {
|
||||
const [checking, setChecking] = useState(false);
|
||||
const [host, setHost] = useState("local");
|
||||
const [deployId, setDeployId] = useState<string | null>(null);
|
||||
const [deployAgentId, setDeployAgentId] = useState<number | undefined>(undefined);
|
||||
|
||||
const existing = useQuery({
|
||||
queryKey: ["stack", id],
|
||||
@@ -80,9 +81,10 @@ export function StackEditor() {
|
||||
qc.invalidateQueries({ queryKey: ["agent-stacks", aid] });
|
||||
toast.success("Saved");
|
||||
if (deploy) {
|
||||
const t = toast.loading("Deploying…");
|
||||
await agentsApi.action(aid, created.id, "start");
|
||||
toast.success("Deployed ✓", { id: t });
|
||||
// Stream the remote deploy live through the agent-deploy WS proxy.
|
||||
setDeployAgentId(aid);
|
||||
setDeployId(created.id);
|
||||
return;
|
||||
}
|
||||
navigate(`/hosts/${aid}/stacks/${created.id}`);
|
||||
return;
|
||||
@@ -251,12 +253,20 @@ export function StackEditor() {
|
||||
{deployId && (
|
||||
<DeployConsole
|
||||
stackId={deployId}
|
||||
agentId={deployAgentId}
|
||||
onClose={() => {
|
||||
const sid = deployId;
|
||||
const aid = deployAgentId;
|
||||
setDeployId(null);
|
||||
qc.invalidateQueries({ queryKey: ["stacks"] });
|
||||
qc.invalidateQueries({ queryKey: ["stack", sid] });
|
||||
navigate(`/stacks/${sid}`);
|
||||
setDeployAgentId(undefined);
|
||||
if (aid != null) {
|
||||
qc.invalidateQueries({ queryKey: ["agent-stacks", aid] });
|
||||
navigate(`/hosts/${aid}/stacks/${sid}`);
|
||||
} else {
|
||||
qc.invalidateQueries({ queryKey: ["stacks"] });
|
||||
qc.invalidateQueries({ queryKey: ["stack", sid] });
|
||||
navigate(`/stacks/${sid}`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user