Skip to content

Commit 31fdf69

Browse files
authored
Merge pull request #3633 from physikal/claude/swarm-container-breakdown-VJhK7
feat(swarm): add container breakdown by node with live metrics
2 parents 3969d2d + f1bc375 commit 31fdf69

File tree

11 files changed

+1122
-7
lines changed

11 files changed

+1122
-7
lines changed

apps/dokploy/components/dashboard/settings/servers/show-swarm-overview-modal.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { useState } from "react";
2+
import { Card } from "@/components/ui/card";
23
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
34
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
5+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
6+
import { ShowSwarmContainers } from "../../swarm/containers/show-swarm-containers";
47
import SwarmMonitorCard from "../../swarm/monitoring-card";
58

69
interface Props {
@@ -21,9 +24,24 @@ export const ShowSwarmOverviewModal = ({ serverId }: Props) => {
2124
</DropdownMenuItem>
2225
</DialogTrigger>
2326
<DialogContent className="sm:max-w-7xl ">
24-
<div className="grid w-full gap-1">
25-
<SwarmMonitorCard serverId={serverId} />
26-
</div>
27+
<Tabs defaultValue="overview">
28+
<TabsList>
29+
<TabsTrigger value="overview">Overview</TabsTrigger>
30+
<TabsTrigger value="containers">Containers</TabsTrigger>
31+
</TabsList>
32+
<TabsContent value="overview">
33+
<div className="grid w-full gap-1">
34+
<SwarmMonitorCard serverId={serverId} />
35+
</div>
36+
</TabsContent>
37+
<TabsContent value="containers">
38+
<Card className="h-full bg-sidebar p-2.5 rounded-xl mx-auto w-full">
39+
<div className="rounded-xl bg-background shadow-md p-6">
40+
<ShowSwarmContainers serverId={serverId} />
41+
</div>
42+
</Card>
43+
</TabsContent>
44+
</Tabs>
2745
</DialogContent>
2846
</Dialog>
2947
);
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { AlertCircle, HardDrive, Network } from "lucide-react";
2+
import { Badge } from "@/components/ui/badge";
3+
import { TableCell, TableRow } from "@/components/ui/table";
4+
import {
5+
Tooltip,
6+
TooltipContent,
7+
TooltipProvider,
8+
TooltipTrigger,
9+
} from "@/components/ui/tooltip";
10+
import type { ContainerInfo, ContainerStat } from "./types";
11+
import { formatCpu, formatIOValue, formatMemUsage } from "./utils";
12+
13+
interface ContainerRowProps {
14+
container: ContainerInfo;
15+
stat: ContainerStat | undefined;
16+
}
17+
18+
export const ContainerRow = ({ container, stat }: ContainerRowProps) => {
19+
const isRunning = container.CurrentState.startsWith("Running");
20+
const hasError = container.Error && container.Error.trim() !== "";
21+
22+
const stateBadge = (
23+
<Badge
24+
variant={hasError ? "destructive" : isRunning ? "default" : "destructive"}
25+
>
26+
{container.CurrentState}
27+
</Badge>
28+
);
29+
30+
return (
31+
<TableRow>
32+
<TableCell>
33+
<div className="flex flex-col gap-1">
34+
<span className="font-medium text-sm">{container.Name}</span>
35+
<span className="text-xs text-muted-foreground truncate max-w-[230px]">
36+
{container.Image}
37+
</span>
38+
</div>
39+
</TableCell>
40+
<TableCell>
41+
{hasError ? (
42+
<TooltipProvider>
43+
<Tooltip>
44+
<TooltipTrigger asChild>
45+
<span className="inline-flex items-center gap-1.5 cursor-help">
46+
{stateBadge}
47+
<AlertCircle className="h-3.5 w-3.5 text-destructive" />
48+
</span>
49+
</TooltipTrigger>
50+
<TooltipContent side="top" className="max-w-xs">
51+
<p className="text-xs font-medium">Error:</p>
52+
<p className="text-xs">{container.Error}</p>
53+
</TooltipContent>
54+
</Tooltip>
55+
</TooltipProvider>
56+
) : (
57+
stateBadge
58+
)}
59+
</TableCell>
60+
<TableCell className="text-right">
61+
{stat ? (
62+
<span className="text-sm font-medium">{formatCpu(stat.CPUPerc)}</span>
63+
) : (
64+
<span className="text-xs text-muted-foreground">--</span>
65+
)}
66+
</TableCell>
67+
<TableCell className="text-right">
68+
{stat ? (
69+
<span className="text-sm font-medium">
70+
{formatMemUsage(stat.MemUsage)}
71+
</span>
72+
) : (
73+
<span className="text-xs text-muted-foreground">--</span>
74+
)}
75+
</TableCell>
76+
<TableCell className="text-right">
77+
{stat ? (
78+
<div className="flex items-center justify-end gap-1.5">
79+
<HardDrive className="h-3 w-3 text-muted-foreground" />
80+
<span className="text-sm">{formatIOValue(stat.BlockIO)}</span>
81+
</div>
82+
) : (
83+
<span className="text-xs text-muted-foreground">--</span>
84+
)}
85+
</TableCell>
86+
<TableCell className="text-right">
87+
{stat ? (
88+
<div className="flex items-center justify-end gap-1.5">
89+
<Network className="h-3 w-3 text-muted-foreground" />
90+
<span className="text-sm">{formatIOValue(stat.NetIO)}</span>
91+
</div>
92+
) : (
93+
<span className="text-xs text-muted-foreground">--</span>
94+
)}
95+
</TableCell>
96+
</TableRow>
97+
);
98+
};

0 commit comments

Comments
 (0)