diff --git a/.github/workflows/sync-version.yml b/.github/workflows/sync-version.yml index 4ef94f1a93..be19a2bb1b 100644 --- a/.github/workflows/sync-version.yml +++ b/.github/workflows/sync-version.yml @@ -3,6 +3,7 @@ name: Sync version to MCP and CLI repos on: release: types: [published] + workflow_dispatch: jobs: sync-version: @@ -15,55 +16,55 @@ jobs: - name: Get version id: get_version run: | - VERSION=$(jq -r .version apps/dokploy/package.json) + VERSION=$(jq -r .version apps/dokploy/package.json | sed 's/^v//') echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Version: $VERSION" - name: Sync version to MCP repository run: | - git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/mcp.git mcp-repo - cd mcp-repo - - # Bump version - jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp - mv package.json.tmp package.json - + git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/mcp.git /tmp/mcp-repo + cd /tmp/mcp-repo + # Regenerate tools from latest OpenAPI spec npm install -g pnpm pnpm install pnpm run fetch-openapi pnpm run generate - + + # Bump version after install so pnpm install doesn't overwrite it + jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp + mv package.json.tmp package.json + git config user.name "Dokploy Bot" git config user.email "bot@dokploy.com" - + git add -A git commit -m "chore: bump version to ${{ steps.get_version.outputs.version }}" \ -m "Source: ${{ github.repository }}@${{ github.sha }}" \ -m "Release: ${{ github.event.release.html_url }}" \ --allow-empty - + git push - name: Sync version to CLI repository run: | - git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/cli.git cli-repo - - cd cli-repo + git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/cli.git /tmp/cli-repo - # Bump version - if [ -f package.json ]; then - jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp - mv package.json.tmp package.json - fi + cd /tmp/cli-repo # Copy latest openapi spec and regenerate commands - cp ../openapi.json ./openapi.json + cp ${{ github.workspace }}/openapi.json ./openapi.json npm install -g pnpm pnpm install pnpm run generate + # Bump version after install so pnpm install doesn't overwrite it + if [ -f package.json ]; then + jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp + mv package.json.tmp package.json + fi + git config user.name "Dokploy Bot" git config user.email "bot@dokploy.com" diff --git a/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx b/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx index 655f9d1470..5edcbe3a7a 100644 --- a/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx +++ b/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx @@ -666,7 +666,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
Custom Entrypoint - Use custom entrypoint for domina + Use custom entrypoint for domain
"web" and/or "websecure" is used by default.
diff --git a/apps/dokploy/components/dashboard/home/show-home.tsx b/apps/dokploy/components/dashboard/home/show-home.tsx new file mode 100644 index 0000000000..f77b71f716 --- /dev/null +++ b/apps/dokploy/components/dashboard/home/show-home.tsx @@ -0,0 +1,291 @@ +import { formatDistanceToNow } from "date-fns"; +import { ArrowRight, Rocket, Server } from "lucide-react"; +import Link from "next/link"; +import { useMemo } from "react"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { api } from "@/utils/api"; + +type DeploymentStatus = "idle" | "running" | "done" | "error"; + +const statusDotClass: Record = { + done: "bg-emerald-500", + running: "bg-amber-500", + error: "bg-red-500", + idle: "bg-muted-foreground/40", +}; + +function getServiceInfo(d: any) { + const app = d.application; + const comp = d.compose; + const serverName: string = + d.server?.name ?? app?.server?.name ?? comp?.server?.name ?? "Dokploy"; + if (app?.environment?.project && app.environment) { + return { + name: app.name as string, + environment: app.environment.name as string, + projectName: app.environment.project.name as string, + serverName, + href: `/dashboard/project/${app.environment.project.projectId}/environment/${app.environment.environmentId}/services/application/${app.applicationId}`, + }; + } + if (comp?.environment?.project && comp.environment) { + return { + name: comp.name as string, + environment: comp.environment.name as string, + projectName: comp.environment.project.name as string, + serverName, + href: `/dashboard/project/${comp.environment.project.projectId}/environment/${comp.environment.environmentId}/services/compose/${comp.composeId}`, + }; + } + return null; +} + +function StatCard({ + label, + value, + delta, +}: { + label: string; + value: string; + delta?: string; +}) { + return ( +
+ + {label} + +
+ {value} + {delta && ( + {delta} + )} +
+
+ ); +} + +function StatusListCard({ + label, + items, +}: { + label: string; + items: { dotClass: string; label: string; count: number }[]; +}) { + return ( +
+ + {label} + +
    + {items.map((item) => ( +
  • + + {item.count} + {item.label} +
  • + ))} +
+
+ ); +} + +export const ShowHome = () => { + const { data: auth } = api.user.get.useQuery(); + const { data: homeStats } = api.project.homeStats.useQuery(); + const { data: permissions } = api.user.getPermissions.useQuery(); + const canReadDeployments = !!permissions?.deployment.read; + const { data: deployments } = api.deployment.allCentralized.useQuery( + undefined, + { + enabled: canReadDeployments, + refetchInterval: 10000, + }, + ); + + const firstName = auth?.user?.firstName?.trim(); + + const totals = homeStats ?? { + projects: 0, + environments: 0, + applications: 0, + compose: 0, + databases: 0, + services: 0, + }; + const statusBreakdown = homeStats?.status ?? { + running: 0, + error: 0, + idle: 0, + }; + + const recentDeployments = useMemo(() => { + if (!deployments) return []; + return [...deployments] + .sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ) + .slice(0, 10); + }, [deployments]); + + const deployStats = useMemo(() => { + const now = Date.now(); + const weekMs = 7 * 24 * 60 * 60 * 1000; + const lastStart = now - weekMs; + const prevStart = now - 2 * weekMs; + + const last: NonNullable = []; + const prev: NonNullable = []; + for (const d of deployments ?? []) { + const t = new Date(d.createdAt).getTime(); + if (t >= lastStart) last.push(d); + else if (t >= prevStart) prev.push(d); + } + + const lastCount = last.length; + const prevCount = prev.length; + let delta: string | undefined; + if (prevCount > 0) { + const pct = Math.round(((lastCount - prevCount) / prevCount) * 100); + delta = `${pct >= 0 ? "+" : ""}${pct}% vs prev 7d`; + } else if (lastCount > 0) { + delta = "no prior data"; + } else { + delta = "no activity yet"; + } + + return { value: String(lastCount), delta }; + }, [deployments]); + + return ( +
+ +
+
+

+ {firstName ? `Welcome back, ${firstName}` : "Welcome back"} +

+ +
+ +
+ + + + +
+ +
+
+
+ +

Recent deployments

+
+ {canReadDeployments && ( + + view all → + + )} +
+ {!canReadDeployments ? ( +
+ + You do not have permission to view deployments. +
+ ) : recentDeployments.length === 0 ? ( +
+ + No deployments yet. +
+ ) : ( +
    + {recentDeployments.map((d) => { + const info = getServiceInfo(d); + if (!info) return null; + const status = (d.status ?? "idle") as DeploymentStatus; + return ( +
  • + + +
    + {info.name} + + {info.projectName} · {info.environment} + +
    + + + {info.serverName} + + + {status} + + + {formatDistanceToNow(new Date(d.createdAt), { + addSuffix: true, + })} + + + logs → + + +
  • + ); + })} +
+ )} +
+
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index 1d3055e5e2..f04f38bcc6 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -166,6 +166,7 @@ export const ShowProjects = () => { return ( total + (env.applications?.length || 0) + + (env.libsql?.length || 0) + (env.mariadb?.length || 0) + (env.mongo?.length || 0) + (env.mysql?.length || 0) + @@ -178,6 +179,7 @@ export const ShowProjects = () => { return ( total + (env.applications?.length || 0) + + (env.libsql?.length || 0) + (env.mariadb?.length || 0) + (env.mongo?.length || 0) + (env.mysql?.length || 0) + diff --git a/apps/dokploy/components/dashboard/search-command.tsx b/apps/dokploy/components/dashboard/search-command.tsx index 9946a54a9f..841728dc10 100644 --- a/apps/dokploy/components/dashboard/search-command.tsx +++ b/apps/dokploy/components/dashboard/search-command.tsx @@ -167,7 +167,7 @@ export const SearchCommand = () => {