Skip to content

Commit bad9731

Browse files
authored
Merge pull request #4261 from Dokploy/canary
🚀 Release v0.29.1
2 parents 7e13243 + 98a5864 commit bad9731

37 files changed

Lines changed: 584 additions & 62 deletions

File tree

.github/workflows/sync-version.yml

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name: Sync version to MCP and CLI repos
33
on:
44
release:
55
types: [published]
6+
workflow_dispatch:
67

78
jobs:
89
sync-version:
@@ -15,55 +16,55 @@ jobs:
1516
- name: Get version
1617
id: get_version
1718
run: |
18-
VERSION=$(jq -r .version apps/dokploy/package.json)
19+
VERSION=$(jq -r .version apps/dokploy/package.json | sed 's/^v//')
1920
echo "version=$VERSION" >> $GITHUB_OUTPUT
2021
echo "Version: $VERSION"
2122
2223
- name: Sync version to MCP repository
2324
run: |
24-
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/mcp.git mcp-repo
25-
cd mcp-repo
26-
27-
# Bump version
28-
jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp
29-
mv package.json.tmp package.json
30-
25+
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/mcp.git /tmp/mcp-repo
26+
cd /tmp/mcp-repo
27+
3128
# Regenerate tools from latest OpenAPI spec
3229
npm install -g pnpm
3330
pnpm install
3431
pnpm run fetch-openapi
3532
pnpm run generate
36-
33+
34+
# Bump version after install so pnpm install doesn't overwrite it
35+
jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp
36+
mv package.json.tmp package.json
37+
3738
git config user.name "Dokploy Bot"
3839
git config user.email "bot@dokploy.com"
39-
40+
4041
git add -A
4142
git commit -m "chore: bump version to ${{ steps.get_version.outputs.version }}" \
4243
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
4344
-m "Release: ${{ github.event.release.html_url }}" \
4445
--allow-empty
45-
46+
4647
git push
4748
4849
4950
- name: Sync version to CLI repository
5051
run: |
51-
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/cli.git cli-repo
52-
53-
cd cli-repo
52+
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/cli.git /tmp/cli-repo
5453
55-
# Bump version
56-
if [ -f package.json ]; then
57-
jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp
58-
mv package.json.tmp package.json
59-
fi
54+
cd /tmp/cli-repo
6055
6156
# Copy latest openapi spec and regenerate commands
62-
cp ../openapi.json ./openapi.json
57+
cp ${{ github.workspace }}/openapi.json ./openapi.json
6358
npm install -g pnpm
6459
pnpm install
6560
pnpm run generate
6661
62+
# Bump version after install so pnpm install doesn't overwrite it
63+
if [ -f package.json ]; then
64+
jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp
65+
mv package.json.tmp package.json
66+
fi
67+
6768
git config user.name "Dokploy Bot"
6869
git config user.email "bot@dokploy.com"
6970

apps/dokploy/components/dashboard/application/domains/handle-domain.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
666666
<div className="space-y-0.5">
667667
<FormLabel>Custom Entrypoint</FormLabel>
668668
<FormDescription>
669-
Use custom entrypoint for domina
669+
Use custom entrypoint for domain
670670
<br />
671671
"web" and/or "websecure" is used by default.
672672
</FormDescription>
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
import { formatDistanceToNow } from "date-fns";
2+
import { ArrowRight, Rocket, Server } from "lucide-react";
3+
import Link from "next/link";
4+
import { useMemo } from "react";
5+
import { Button } from "@/components/ui/button";
6+
import { Card } from "@/components/ui/card";
7+
import { api } from "@/utils/api";
8+
9+
type DeploymentStatus = "idle" | "running" | "done" | "error";
10+
11+
const statusDotClass: Record<string, string> = {
12+
done: "bg-emerald-500",
13+
running: "bg-amber-500",
14+
error: "bg-red-500",
15+
idle: "bg-muted-foreground/40",
16+
};
17+
18+
function getServiceInfo(d: any) {
19+
const app = d.application;
20+
const comp = d.compose;
21+
const serverName: string =
22+
d.server?.name ?? app?.server?.name ?? comp?.server?.name ?? "Dokploy";
23+
if (app?.environment?.project && app.environment) {
24+
return {
25+
name: app.name as string,
26+
environment: app.environment.name as string,
27+
projectName: app.environment.project.name as string,
28+
serverName,
29+
href: `/dashboard/project/${app.environment.project.projectId}/environment/${app.environment.environmentId}/services/application/${app.applicationId}`,
30+
};
31+
}
32+
if (comp?.environment?.project && comp.environment) {
33+
return {
34+
name: comp.name as string,
35+
environment: comp.environment.name as string,
36+
projectName: comp.environment.project.name as string,
37+
serverName,
38+
href: `/dashboard/project/${comp.environment.project.projectId}/environment/${comp.environment.environmentId}/services/compose/${comp.composeId}`,
39+
};
40+
}
41+
return null;
42+
}
43+
44+
function StatCard({
45+
label,
46+
value,
47+
delta,
48+
}: {
49+
label: string;
50+
value: string;
51+
delta?: string;
52+
}) {
53+
return (
54+
<div className="rounded-xl border bg-background p-5 min-h-[140px] flex flex-col justify-between">
55+
<span className="text-xs uppercase tracking-wider text-muted-foreground">
56+
{label}
57+
</span>
58+
<div className="flex flex-col gap-1">
59+
<span className="text-3xl font-semibold tracking-tight">{value}</span>
60+
{delta && (
61+
<span className="text-xs text-muted-foreground">{delta}</span>
62+
)}
63+
</div>
64+
</div>
65+
);
66+
}
67+
68+
function StatusListCard({
69+
label,
70+
items,
71+
}: {
72+
label: string;
73+
items: { dotClass: string; label: string; count: number }[];
74+
}) {
75+
return (
76+
<div className="rounded-xl border bg-background p-5 min-h-[140px] flex flex-col gap-3">
77+
<span className="text-xs uppercase tracking-wider text-muted-foreground">
78+
{label}
79+
</span>
80+
<ul className="flex flex-col gap-1.5">
81+
{items.map((item) => (
82+
<li key={item.label} className="flex items-center gap-2.5 text-sm">
83+
<span
84+
className={`size-2 rounded-full shrink-0 ${item.dotClass}`}
85+
aria-hidden
86+
/>
87+
<span className="font-semibold tabular-nums w-8">{item.count}</span>
88+
<span className="text-muted-foreground">{item.label}</span>
89+
</li>
90+
))}
91+
</ul>
92+
</div>
93+
);
94+
}
95+
96+
export const ShowHome = () => {
97+
const { data: auth } = api.user.get.useQuery();
98+
const { data: homeStats } = api.project.homeStats.useQuery();
99+
const { data: permissions } = api.user.getPermissions.useQuery();
100+
const canReadDeployments = !!permissions?.deployment.read;
101+
const { data: deployments } = api.deployment.allCentralized.useQuery(
102+
undefined,
103+
{
104+
enabled: canReadDeployments,
105+
refetchInterval: 10000,
106+
},
107+
);
108+
109+
const firstName = auth?.user?.firstName?.trim();
110+
111+
const totals = homeStats ?? {
112+
projects: 0,
113+
environments: 0,
114+
applications: 0,
115+
compose: 0,
116+
databases: 0,
117+
services: 0,
118+
};
119+
const statusBreakdown = homeStats?.status ?? {
120+
running: 0,
121+
error: 0,
122+
idle: 0,
123+
};
124+
125+
const recentDeployments = useMemo(() => {
126+
if (!deployments) return [];
127+
return [...deployments]
128+
.sort(
129+
(a, b) =>
130+
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
131+
)
132+
.slice(0, 10);
133+
}, [deployments]);
134+
135+
const deployStats = useMemo(() => {
136+
const now = Date.now();
137+
const weekMs = 7 * 24 * 60 * 60 * 1000;
138+
const lastStart = now - weekMs;
139+
const prevStart = now - 2 * weekMs;
140+
141+
const last: NonNullable<typeof deployments> = [];
142+
const prev: NonNullable<typeof deployments> = [];
143+
for (const d of deployments ?? []) {
144+
const t = new Date(d.createdAt).getTime();
145+
if (t >= lastStart) last.push(d);
146+
else if (t >= prevStart) prev.push(d);
147+
}
148+
149+
const lastCount = last.length;
150+
const prevCount = prev.length;
151+
let delta: string | undefined;
152+
if (prevCount > 0) {
153+
const pct = Math.round(((lastCount - prevCount) / prevCount) * 100);
154+
delta = `${pct >= 0 ? "+" : ""}${pct}% vs prev 7d`;
155+
} else if (lastCount > 0) {
156+
delta = "no prior data";
157+
} else {
158+
delta = "no activity yet";
159+
}
160+
161+
return { value: String(lastCount), delta };
162+
}, [deployments]);
163+
164+
return (
165+
<div className="w-full">
166+
<Card className="h-full bg-sidebar p-2.5 rounded-xl min-h-[85vh]">
167+
<div className="rounded-xl bg-background shadow-md p-6 flex flex-col gap-6 h-full">
168+
<div className="flex flex-col gap-6 sm:flex-row sm:items-end sm:justify-between">
169+
<h1 className="text-3xl font-semibold tracking-tight">
170+
{firstName ? `Welcome back, ${firstName}` : "Welcome back"}
171+
</h1>
172+
<Button asChild variant="secondary" className="w-fit">
173+
<Link href="/dashboard/projects">
174+
Go to projects
175+
<ArrowRight className="size-4" />
176+
</Link>
177+
</Button>
178+
</div>
179+
180+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
181+
<StatCard
182+
label="Projects"
183+
value={String(totals.projects)}
184+
delta={`${totals.environments} ${totals.environments === 1 ? "environment" : "environments"}`}
185+
/>
186+
<StatCard
187+
label="Services"
188+
value={String(totals.services)}
189+
delta={`${totals.applications} apps · ${totals.compose} compose · ${totals.databases} db`}
190+
/>
191+
<StatCard
192+
label="Deploys / 7d"
193+
value={deployStats.value}
194+
delta={deployStats.delta}
195+
/>
196+
<StatusListCard
197+
label="Status"
198+
items={[
199+
{
200+
dotClass: "bg-emerald-500",
201+
label: "running",
202+
count: statusBreakdown.running,
203+
},
204+
{
205+
dotClass: "bg-red-500",
206+
label: "errored",
207+
count: statusBreakdown.error,
208+
},
209+
{
210+
dotClass: "bg-muted-foreground/40",
211+
label: "idle",
212+
count: statusBreakdown.idle,
213+
},
214+
]}
215+
/>
216+
</div>
217+
218+
<div className="rounded-xl border bg-background">
219+
<div className="flex items-center justify-between px-5 py-4 border-b">
220+
<div className="flex items-center gap-2">
221+
<Rocket className="size-4 text-muted-foreground" />
222+
<h2 className="text-sm font-semibold">Recent deployments</h2>
223+
</div>
224+
{canReadDeployments && (
225+
<Link
226+
href="/dashboard/deployments"
227+
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
228+
>
229+
view all →
230+
</Link>
231+
)}
232+
</div>
233+
{!canReadDeployments ? (
234+
<div className="min-h-[400px] flex flex-col items-center justify-center gap-3 text-center text-sm text-muted-foreground p-10">
235+
<Rocket className="size-8 opacity-40" />
236+
<span>You do not have permission to view deployments.</span>
237+
</div>
238+
) : recentDeployments.length === 0 ? (
239+
<div className="min-h-[400px] flex flex-col items-center justify-center gap-3 text-center text-sm text-muted-foreground p-10">
240+
<Rocket className="size-8 opacity-40" />
241+
<span>No deployments yet.</span>
242+
</div>
243+
) : (
244+
<ul className="divide-y">
245+
{recentDeployments.map((d) => {
246+
const info = getServiceInfo(d);
247+
if (!info) return null;
248+
const status = (d.status ?? "idle") as DeploymentStatus;
249+
return (
250+
<li key={d.deploymentId}>
251+
<Link
252+
href={info.href}
253+
className="flex items-center gap-4 px-5 py-4 hover:bg-muted/40 transition-colors"
254+
>
255+
<span
256+
className={`size-2 rounded-full shrink-0 ${statusDotClass[status] ?? statusDotClass.idle}`}
257+
aria-hidden
258+
/>
259+
<div className="flex flex-col min-w-0 flex-1">
260+
<span className="text-sm truncate">{info.name}</span>
261+
<span className="text-xs text-muted-foreground truncate">
262+
{info.projectName} · {info.environment}
263+
</span>
264+
</div>
265+
<span className="text-xs text-muted-foreground w-36 hidden lg:flex items-center justify-end gap-1.5 truncate">
266+
<Server className="size-3 shrink-0" />
267+
<span className="truncate">{info.serverName}</span>
268+
</span>
269+
<span className="text-xs text-muted-foreground w-20 text-right hidden sm:inline">
270+
{status}
271+
</span>
272+
<span className="text-xs text-muted-foreground w-24 text-right hidden md:inline">
273+
{formatDistanceToNow(new Date(d.createdAt), {
274+
addSuffix: true,
275+
})}
276+
</span>
277+
<span className="text-xs text-muted-foreground hover:text-foreground transition-colors">
278+
logs →
279+
</span>
280+
</Link>
281+
</li>
282+
);
283+
})}
284+
</ul>
285+
)}
286+
</div>
287+
</div>
288+
</Card>
289+
</div>
290+
);
291+
};

0 commit comments

Comments
 (0)