Phase 11: remote UX & network attach (0.11.0)

- Live remote-stack logs over a WebSocket proxied through the central app to
  the agent (/ws/agent-logs/{agent}/{stack}); agent gains a WS log endpoint.
- Deploy to a remote host from the UI: host selector in the New Stack editor
  and template dialog; templates instantiate onto an agent via the proxy.
- Network attach/detach: expandable inspect view per network with
  connect/disconnect + container picker; GET /{id}/containers, POST connect/disconnect.
- Remove dead pages/Placeholder.tsx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
menzelj
2026-06-08 07:49:20 +00:00
co-authored by Claude Opus 4.8
parent ec7e3e706f
commit 1931500c24
18 changed files with 492 additions and 67 deletions
+4
View File
@@ -17,6 +17,10 @@ export const agentsApi = {
stacks: (id: number) =>
api.get<RemoteStackSummary[]>(`/api/agents/${id}/stacks`).then((r) => r.data),
createStack: (id: number, body: { name: string; yaml: string; env?: string }) =>
api
.post<{ id: string; name: string }>(`/api/agents/${id}/stacks`, body)
.then((r) => r.data),
stack: (id: number, stackId: string) =>
api.get<RemoteStackDetail>(`/api/agents/${id}/stacks/${stackId}`).then((r) => r.data),
logs: (id: number, stackId: string, tail = 200) =>
+15
View File
@@ -26,10 +26,25 @@ export interface NetworkCreate {
attachable: boolean;
}
export interface NetworkContainer {
id: string;
name: string;
state: string;
stack: string | null;
connected: boolean;
}
export const networksApi = {
list: () => api.get<NetworkInfo[]>("/api/networks").then((r) => r.data),
inspect: (id: string) => api.get<NetworkInfo>(`/api/networks/${id}`).then((r) => r.data),
containers: (id: string) =>
api.get<NetworkContainer[]>(`/api/networks/${id}/containers`).then((r) => r.data),
create: (body: NetworkCreate) =>
api.post<NetworkInfo>("/api/networks", body).then((r) => r.data),
connect: (id: string, container: string, aliases?: string[]) =>
api.post(`/api/networks/${id}/connect`, { container, aliases }).then((r) => r.data),
disconnect: (id: string, container: string, force = false) =>
api.post(`/api/networks/${id}/disconnect`, { container, force }).then((r) => r.data),
remove: (id: string) => api.delete(`/api/networks/${id}`).then((r) => r.data),
prune: () =>
api.post<{ NetworksDeleted: string[] | null }>("/api/networks/prune").then((r) => r.data),
+10 -5
View File
@@ -24,12 +24,17 @@ export const templatesApi = {
list: () => api.get<TemplateSummary[]>("/api/templates").then((r) => r.data),
get: (id: string) =>
api.get<TemplateDetail>(`/api/templates/${id}`).then((r) => r.data),
instantiate: (id: string, name: string, values: Record<string, string>) =>
instantiate: (
id: string,
name: string,
values: Record<string, string>,
agentId?: number | null
) =>
api
.post<{ id: string; name: string }>(`/api/templates/${id}/instantiate`, {
name,
values,
})
.post<{ id: string; name: string; agent_id: number | null }>(
`/api/templates/${id}/instantiate`,
{ name, values, agent_id: agentId ?? null }
)
.then((r) => r.data),
save: (body: { name: string; description?: string; tags: string[]; yaml: string }) =>
api.post("/api/templates", body).then((r) => r.data),