Stacks page: table layout with usage meters, matching the dashboard (0.21.4)

The Stacks tab now lists stacks in the shared StacksTable (status, CPU
and memory meters, inline start/stop/restart) instead of cards, for both
the local host and per-agent sections — same look as the dashboard.
StacksTable gained optional showEdit/showDelete props so the management
surface keeps the Edit link and local Delete (with confirm). Search and
sort are unchanged. Removed the now-unused StackCard component.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
menzelj
2026-06-08 16:56:53 +00:00
co-authored by Claude Opus 4.8
parent ffb4ee39a0
commit 8fc61b1531
7 changed files with 120 additions and 201 deletions
+29 -23
View File
@@ -2,11 +2,12 @@ import { useMemo, useState } from "react";
import { Link } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { Plus, Search, HardDrive } from "lucide-react";
import { Button, Input, Spinner, Card } from "@/components/ui";
import { StackCard } from "@/components/stacks/StackCard";
import { Button, Input } from "@/components/ui";
import { StacksTable } from "@/components/stacks/StacksTable";
import { RestoreButton } from "@/components/stacks/BackupRestore";
import { AgentStacksSection } from "@/components/stacks/AgentStacksSection";
import { stacksApi } from "@/api/stacks";
import { systemApi } from "@/api/system";
import { agentsApi } from "@/api/agents";
import { useAuthStore } from "@/store/auth";
import { useStackActions } from "@/hooks/useStackActions";
@@ -25,6 +26,17 @@ export function Stacks() {
refetchInterval: 5000,
});
const stats = useQuery({
queryKey: ["stack-stats"],
queryFn: stacksApi.stats,
refetchInterval: 5000,
});
const info = useQuery({
queryKey: ["system"],
queryFn: systemApi.info,
refetchInterval: 5000,
});
const agents = useQuery({
queryKey: ["agents"],
queryFn: () => agentsApi.list(),
@@ -83,27 +95,21 @@ export function Stacks() {
<HardDrive className="h-4 w-4" /> This host
</h2>
)}
{isLoading ? (
<Spinner />
) : filtered.length > 0 ? (
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
{filtered.map((s) => (
<StackCard
key={s.id}
stack={s}
isAdmin={isAdmin}
busy={busyId === s.id}
onStart={start}
onStop={stop}
onRestart={restart}
/>
))}
</div>
) : (
<Card>
<p className="text-sm text-slate-500">No stacks match your search.</p>
</Card>
)}
<StacksTable
stacks={filtered}
stats={stats.data}
hostCpus={info.data?.cpu_cores ?? 0}
hostMem={info.data?.ram.total ?? 0}
isAdmin={isAdmin}
busyId={busyId}
loading={isLoading}
showEdit
showDelete
onStart={start}
onStop={stop}
onRestart={restart}
emptyText={q ? "No stacks match your search." : "No stacks yet. Create one with “New Stack”."}
/>
</section>
{agents.data?.map((agent) => (