Phase 20: per-container inspect + start/stop/restart, local + agent (0.26.0)
Stack Overview now renders each service as an expandable ContainerCard with a
curated single-container inspect view and admin start/stop/restart buttons,
both for local stacks (GET/POST /api/containers/{id}[/{action}]) and remote
stacks (proxied via /api/agents/{id}/containers/* to the agent's new
/agent/containers/* endpoints). Only compose-managed containers are exposed.
Also bumps version 0.23.0 -> 0.26.0 (the bumps for the already-committed
Phase 18 image-prune / Phase 19 compose-validate were missed) and backfills
README sections for Phase 18/19/20.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9c4d319f8f
commit
2f63247fc1
@@ -15,12 +15,13 @@ import { toast } from "sonner";
|
||||
import { Badge, Button, Card, Spinner, StatusDot } from "@/components/ui";
|
||||
import { ConfirmDialog } from "@/components/ui/ConfirmDialog";
|
||||
import { LogViewer } from "@/components/stacks/LogViewer";
|
||||
import { ContainerPorts } from "@/components/stacks/ContainerPorts";
|
||||
import { ContainerCard } from "@/components/stacks/ContainerCard";
|
||||
import { BackupButton } from "@/components/stacks/BackupRestore";
|
||||
import { stacksApi } from "@/api/stacks";
|
||||
import { apiErrorMessage } from "@/api/client";
|
||||
import { useAuthStore } from "@/store/auth";
|
||||
import { useStackActions } from "@/hooks/useStackActions";
|
||||
import type { ContainerInfo } from "@/types";
|
||||
|
||||
const TABS = ["Overview", "Logs", "Environment", "Compose"] as const;
|
||||
type Tab = (typeof TABS)[number];
|
||||
@@ -30,6 +31,7 @@ export function StackDetail() {
|
||||
const isAdmin = useAuthStore((s) => s.user?.role === "admin");
|
||||
const [tab, setTab] = useState<Tab>("Overview");
|
||||
const actions = useStackActions();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ["stack", id],
|
||||
@@ -102,7 +104,13 @@ export function StackDetail() {
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{tab === "Overview" && <Overview data={data} />}
|
||||
{tab === "Overview" && (
|
||||
<Overview
|
||||
data={data}
|
||||
isAdmin={isAdmin}
|
||||
onChanged={() => queryClient.invalidateQueries({ queryKey: ["stack", id] })}
|
||||
/>
|
||||
)}
|
||||
{tab === "Logs" && <LogViewer stackId={id} />}
|
||||
{tab === "Environment" && <EnvView env={data.env} />}
|
||||
{tab === "Compose" && <ComposeView yaml={data.yaml} />}
|
||||
@@ -111,7 +119,15 @@ export function StackDetail() {
|
||||
);
|
||||
}
|
||||
|
||||
function Overview({ data }: { data: ReturnType<typeof Object> & any }) {
|
||||
function Overview({
|
||||
data,
|
||||
isAdmin,
|
||||
onChanged,
|
||||
}: {
|
||||
data: ReturnType<typeof Object> & any;
|
||||
isAdmin: boolean;
|
||||
onChanged: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-2 overflow-auto">
|
||||
{data.containers.length === 0 && (
|
||||
@@ -121,21 +137,8 @@ function Overview({ data }: { data: ReturnType<typeof Object> & any }) {
|
||||
</p>
|
||||
</Card>
|
||||
)}
|
||||
{data.containers.map((c: any) => (
|
||||
<Card key={c.id} className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<StatusDot status={c.state === "running" ? "running" : "stopped"} />
|
||||
<div>
|
||||
<p className="font-medium">{c.service}</p>
|
||||
<p className="font-mono text-xs text-slate-500">{c.image}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xs text-slate-500">
|
||||
{c.health && <Badge>{c.health}</Badge>}
|
||||
<span>{c.status}</span>
|
||||
<ContainerPorts ports={c.ports} />
|
||||
</div>
|
||||
</Card>
|
||||
{data.containers.map((c: ContainerInfo) => (
|
||||
<ContainerCard key={c.id} container={c} isAdmin={isAdmin} onChanged={onChanged} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user