Skip to content

Commit 8b3e10d

Browse files
Merge branch 'main' of github.com:sizzldev/ctrlplane
2 parents 6da46b2 + 09e2275 commit 8b3e10d

File tree

6 files changed

+584
-305
lines changed

6 files changed

+584
-305
lines changed

apps/web/app/routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ export default [
1111
"deployments/:deploymentId",
1212
"routes/deployments/page.$deploymentId.tsx",
1313
),
14+
route(
15+
"deployments/:deploymentId/versions",
16+
"routes/deployments/page.$deploymentId.versions.tsx",
17+
),
1418
route("resources", "routes/resources.tsx"),
19+
route("relationship-rules", "routes/relationship-rules.tsx"),
1520
]),
1621
] satisfies RouteConfig;

apps/web/app/routes/_layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const navigationGroups = [
5050
{
5151
label: "Orchestration",
5252
items: [
53-
{ title: "Systems", to: "/systems", icon: LayoutDashboard },
53+
{ title: "Projects", to: "/projects", icon: LayoutDashboard },
5454
{ title: "Deployments", to: "/deployments", icon: Rocket },
5555
{ title: "Runners", to: "/runners", icon: Cpu },
5656
{ title: "Policies", to: "/policies", icon: ShieldCheck },

apps/web/app/routes/deployments/page.$deploymentId.tsx

Lines changed: 47 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
import type { Edge, Node } from "reactflow";
2-
import { useCallback, useMemo, useState } from "react";
3-
import { ArrowLeft } from "lucide-react";
2+
import { useCallback, useMemo } from "react";
43
import { Link, useParams, useSearchParams } from "react-router";
54

6-
import { Button } from "~/components/ui/button";
5+
import {
6+
Breadcrumb,
7+
BreadcrumbItem,
8+
BreadcrumbList,
9+
BreadcrumbPage,
10+
BreadcrumbSeparator,
11+
} from "~/components/ui/breadcrumb";
712
import {
813
ResizableHandle,
914
ResizablePanel,
1015
ResizablePanelGroup,
1116
} from "~/components/ui/resizable";
17+
import { Separator } from "~/components/ui/separator";
18+
import { SidebarTrigger } from "~/components/ui/sidebar";
1219
import { Tabs, TabsList, TabsTrigger } from "~/components/ui/tabs";
1320
import { DeploymentFlow } from "./_components/DeploymentFlow";
1421
import { mockDeploymentDetail, mockEnvironments } from "./_components/mockData";
@@ -27,7 +34,6 @@ export default function DeploymentDetail() {
2734
const selectedVersionId = searchParams.get("version");
2835

2936
const _deploymentId = useParams().deploymentId;
30-
const [selectedTab, setSelectedTab] = useState("environments");
3137

3238
// In a real app, fetch deployment data based on deploymentId
3339
const deployment = mockDeploymentDetail;
@@ -174,50 +180,45 @@ export default function DeploymentDetail() {
174180
}, [environments]);
175181

176182
return (
177-
<div className="">
178-
<header className="shrink-0 border-b">
179-
<div className="flex items-center justify-between gap-8 px-4 py-6">
180-
<div className="flex items-center gap-4">
181-
<div>
182-
<Link to="/deployments">
183-
<Button variant="ghost" size="icon" className="h-8 w-8">
184-
<ArrowLeft className="h-4 w-4" />
185-
</Button>
186-
</Link>
187-
</div>
188-
189-
<div className="space-y-1">
190-
<h1 className="text-xl font-bold">{deployment.name}</h1>
191-
<p className="text-sm text-muted-foreground">
192-
{deployment.description}
193-
</p>
194-
</div>
195-
</div>
183+
<>
184+
<header className="flex h-16 shrink-0 items-center justify-between gap-2 border-b pr-4">
185+
<div className="flex items-center gap-2 px-4">
186+
<SidebarTrigger className="-ml-1" />
187+
<Separator
188+
orientation="vertical"
189+
className="mr-2 data-[orientation=vertical]:h-4"
190+
/>
191+
<Breadcrumb>
192+
<BreadcrumbList>
193+
<BreadcrumbItem>
194+
<BreadcrumbItem>
195+
<Link to={`/deployments`}>Deployments</Link>
196+
</BreadcrumbItem>
197+
<BreadcrumbSeparator />
198+
<BreadcrumbPage>{deployment.name}</BreadcrumbPage>
199+
</BreadcrumbItem>
200+
</BreadcrumbList>
201+
</Breadcrumb>
202+
</div>
196203

197-
<div>
198-
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
199-
<TabsList>
200-
<TabsTrigger
201-
value="environments"
202-
className="text-base text-muted-foreground data-[state=active]:text-foreground"
203-
>
204-
Environments
205-
</TabsTrigger>
206-
<TabsTrigger
207-
value="versions"
208-
className="text-base text-muted-foreground data-[state=active]:text-foreground"
209-
>
204+
<div className="flex items-center gap-4">
205+
<Tabs value="environments">
206+
<TabsList>
207+
<TabsTrigger value="environments" asChild>
208+
<Link to={`/deployments/${deployment.id}`}>Environments</Link>
209+
</TabsTrigger>
210+
<TabsTrigger value="versions" asChild>
211+
<Link to={`/deployments/${deployment.id}/versions`}>
210212
Versions
211-
</TabsTrigger>
212-
<TabsTrigger
213-
value="activity"
214-
className="text-base text-muted-foreground data-[state=active]:text-foreground"
215-
>
213+
</Link>
214+
</TabsTrigger>
215+
<TabsTrigger value="activity" asChild>
216+
<Link to={`/deployments/${deployment.id}/activity`}>
216217
Activity
217-
</TabsTrigger>
218-
</TabsList>
219-
</Tabs>
220-
</div>
218+
</Link>
219+
</TabsTrigger>
220+
</TabsList>
221+
</Tabs>
221222
</div>
222223
</header>
223224

@@ -277,6 +278,6 @@ export default function DeploymentDetail() {
277278
)}
278279
</ResizablePanelGroup>
279280
</div>
280-
</div>
281+
</>
281282
);
282283
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import { useState } from "react";
2+
import { Clock, Rocket, Search } from "lucide-react";
3+
import { Link, useParams } from "react-router";
4+
5+
import { Badge } from "~/components/ui/badge";
6+
import {
7+
Breadcrumb,
8+
BreadcrumbItem,
9+
BreadcrumbList,
10+
BreadcrumbPage,
11+
BreadcrumbSeparator,
12+
} from "~/components/ui/breadcrumb";
13+
import { Button } from "~/components/ui/button";
14+
import { Input } from "~/components/ui/input";
15+
import { Separator } from "~/components/ui/separator";
16+
import { SidebarTrigger } from "~/components/ui/sidebar";
17+
import {
18+
Table,
19+
TableBody,
20+
TableCell,
21+
TableHead,
22+
TableHeader,
23+
TableRow,
24+
} from "~/components/ui/table";
25+
import {
26+
getVersionStatusColor,
27+
getVersionStatusIcon,
28+
} from "./_components/helpers";
29+
import { mockDeploymentDetail } from "./_components/mockData";
30+
31+
export function meta() {
32+
return [
33+
{ title: "Versions - Deployment Details - Ctrlplane" },
34+
{ name: "description", content: "View all deployment versions" },
35+
];
36+
}
37+
38+
export default function DeploymentVersions() {
39+
const _deploymentId = useParams().deploymentId;
40+
41+
// In a real app, fetch deployment data based on deploymentId
42+
const deployment = mockDeploymentDetail;
43+
// Calculate stats for each version
44+
const versionsWithStats = deployment.versions.map((version) => {
45+
const currentReleaseTargets = deployment.releaseTargets.filter(
46+
(rt) => rt.version.currentId === version.id,
47+
);
48+
const desiredReleaseTargets = deployment.releaseTargets.filter(
49+
(rt) => rt.version.desiredId === version.id,
50+
);
51+
const blockedReleaseTargets = deployment.releaseTargets.filter((rt) =>
52+
rt.version.blockedVersions?.some((bv) => bv.versionId === version.id),
53+
);
54+
55+
const currentEnvironments = new Set(
56+
currentReleaseTargets.map((rt) => rt.environment.name),
57+
);
58+
const desiredEnvironments = new Set(
59+
desiredReleaseTargets.map((rt) => rt.environment.name),
60+
);
61+
62+
return {
63+
...version,
64+
currentCount: currentReleaseTargets.length,
65+
desiredCount: desiredReleaseTargets.length,
66+
blockedCount: blockedReleaseTargets.length,
67+
currentEnvironments: Array.from(currentEnvironments),
68+
desiredEnvironments: Array.from(desiredEnvironments),
69+
};
70+
});
71+
72+
const [searchQuery, setSearchQuery] = useState("");
73+
return (
74+
<>
75+
<header className="flex h-16 shrink-0 items-center justify-between gap-2 border-b pr-4">
76+
<div className="flex items-center gap-2 px-4">
77+
<SidebarTrigger className="-ml-1" />
78+
<Separator
79+
orientation="vertical"
80+
className="mr-2 data-[orientation=vertical]:h-4"
81+
/>
82+
<Breadcrumb>
83+
<BreadcrumbList>
84+
<BreadcrumbItem>
85+
<BreadcrumbItem>
86+
<Link to={`/deployments`}>Deployments</Link>
87+
</BreadcrumbItem>
88+
<BreadcrumbSeparator />
89+
<BreadcrumbItem>
90+
<Link to={`/deployments/${deployment.id}`}>
91+
{deployment.name}
92+
</Link>
93+
</BreadcrumbItem>
94+
<BreadcrumbSeparator />
95+
<BreadcrumbPage>Versions</BreadcrumbPage>
96+
</BreadcrumbItem>
97+
</BreadcrumbList>
98+
</Breadcrumb>
99+
</div>
100+
101+
<div className="flex min-w-[350px] items-center gap-4">
102+
<div className="relative flex-1">
103+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
104+
<Input
105+
placeholder="Search resources..."
106+
value={searchQuery}
107+
onChange={(e) => setSearchQuery(e.target.value)}
108+
className="pl-10"
109+
/>
110+
</div>
111+
</div>
112+
</header>
113+
114+
<Table>
115+
<TableHeader>
116+
<TableRow>
117+
<TableHead>Version</TableHead>
118+
<TableHead>Status</TableHead>
119+
<TableHead>Message</TableHead>
120+
<TableHead>Current</TableHead>
121+
<TableHead>Desired</TableHead>
122+
<TableHead>Blocked</TableHead>
123+
<TableHead>Created</TableHead>
124+
<TableHead>Actions</TableHead>
125+
</TableRow>
126+
</TableHeader>
127+
<TableBody>
128+
{versionsWithStats.map((version) => (
129+
<TableRow key={version.id}>
130+
{/* Version Tag */}
131+
<TableCell className="font-mono font-semibold">
132+
{version.tag}
133+
</TableCell>
134+
135+
{/* Status */}
136+
<TableCell>
137+
<Badge className={getVersionStatusColor(version.status)}>
138+
{getVersionStatusIcon(version.status)}
139+
<span className="ml-1 capitalize">{version.status}</span>
140+
</Badge>
141+
</TableCell>
142+
143+
{/* Message */}
144+
<TableCell className="max-w-md">
145+
<span className="line-clamp-1 text-sm text-muted-foreground">
146+
{version.message ?? "-"}
147+
</span>
148+
</TableCell>
149+
150+
{/* Current Deployments */}
151+
<TableCell>
152+
<div className="space-y-1">
153+
<div className="font-medium">{version.currentCount}</div>
154+
{version.currentEnvironments.length > 0 && (
155+
<div className="flex flex-wrap gap-1">
156+
{version.currentEnvironments.map((env) => (
157+
<Badge
158+
key={env}
159+
variant="outline"
160+
className="px-1 py-0 text-xs"
161+
>
162+
{env}
163+
</Badge>
164+
))}
165+
</div>
166+
)}
167+
</div>
168+
</TableCell>
169+
170+
{/* Desired Deployments */}
171+
<TableCell>
172+
<div className="space-y-1">
173+
<div className="font-medium text-blue-600">
174+
{version.desiredCount}
175+
</div>
176+
{version.desiredEnvironments.length > 0 && (
177+
<div className="flex flex-wrap gap-1">
178+
{version.desiredEnvironments.map((env) => (
179+
<Badge
180+
key={env}
181+
variant="outline"
182+
className="border-blue-500/30 bg-blue-500/5 px-1 py-0 text-xs text-blue-600"
183+
>
184+
{env}
185+
</Badge>
186+
))}
187+
</div>
188+
)}
189+
</div>
190+
</TableCell>
191+
192+
{/* Blocked */}
193+
<TableCell>
194+
{version.blockedCount > 0 && (
195+
<Badge className="border-amber-500/20 bg-amber-500/10 text-amber-600">
196+
{version.blockedCount}
197+
</Badge>
198+
)}
199+
</TableCell>
200+
201+
{/* Created */}
202+
<TableCell>
203+
<div className="flex items-center gap-1 text-sm text-muted-foreground">
204+
<Clock className="h-3 w-3" />
205+
{version.createdAt}
206+
</div>
207+
</TableCell>
208+
209+
{/* Actions */}
210+
<TableCell>
211+
<div className="flex gap-2">
212+
<Link
213+
to={`/deployments/${deployment.id}?version=${version.id}`}
214+
>
215+
<Button variant="ghost" size="sm">
216+
View
217+
</Button>
218+
</Link>
219+
{version.desiredCount < deployment.releaseTargets.length && (
220+
<Button size="sm" variant="outline">
221+
<Rocket className="mr-1 h-4 w-4" />
222+
Deploy
223+
</Button>
224+
)}
225+
</div>
226+
</TableCell>
227+
</TableRow>
228+
))}
229+
</TableBody>
230+
</Table>
231+
</>
232+
);
233+
}

0 commit comments

Comments
 (0)