mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
41
.github/workflows/sync-version.yml
vendored
41
.github/workflows/sync-version.yml
vendored
@@ -3,6 +3,7 @@ name: Sync version to MCP and CLI repos
|
|||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync-version:
|
sync-version:
|
||||||
@@ -15,55 +16,55 @@ jobs:
|
|||||||
- name: Get version
|
- name: Get version
|
||||||
id: get_version
|
id: get_version
|
||||||
run: |
|
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" >> $GITHUB_OUTPUT
|
||||||
echo "Version: $VERSION"
|
echo "Version: $VERSION"
|
||||||
|
|
||||||
- name: Sync version to MCP repository
|
- name: Sync version to MCP repository
|
||||||
run: |
|
run: |
|
||||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/mcp.git mcp-repo
|
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/mcp.git /tmp/mcp-repo
|
||||||
cd mcp-repo
|
cd /tmp/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
|
|
||||||
|
|
||||||
# Regenerate tools from latest OpenAPI spec
|
# Regenerate tools from latest OpenAPI spec
|
||||||
npm install -g pnpm
|
npm install -g pnpm
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm run fetch-openapi
|
pnpm run fetch-openapi
|
||||||
pnpm run generate
|
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.name "Dokploy Bot"
|
||||||
git config user.email "bot@dokploy.com"
|
git config user.email "bot@dokploy.com"
|
||||||
|
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "chore: bump version to ${{ steps.get_version.outputs.version }}" \
|
git commit -m "chore: bump version to ${{ steps.get_version.outputs.version }}" \
|
||||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||||
-m "Release: ${{ github.event.release.html_url }}" \
|
-m "Release: ${{ github.event.release.html_url }}" \
|
||||||
--allow-empty
|
--allow-empty
|
||||||
|
|
||||||
git push
|
git push
|
||||||
|
|
||||||
|
|
||||||
- name: Sync version to CLI repository
|
- name: Sync version to CLI repository
|
||||||
run: |
|
run: |
|
||||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/cli.git cli-repo
|
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/cli.git /tmp/cli-repo
|
||||||
|
|
||||||
cd cli-repo
|
cd /tmp/cli-repo
|
||||||
|
|
||||||
# Bump version
|
# Copy latest openapi spec and regenerate commands
|
||||||
|
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
|
if [ -f package.json ]; then
|
||||||
jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
||||||
mv package.json.tmp package.json
|
mv package.json.tmp package.json
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Copy latest openapi spec and regenerate commands
|
|
||||||
cp ../openapi.json ./openapi.json
|
|
||||||
npm install -g pnpm
|
|
||||||
pnpm install
|
|
||||||
pnpm run generate
|
|
||||||
|
|
||||||
git config user.name "Dokploy Bot"
|
git config user.name "Dokploy Bot"
|
||||||
git config user.email "bot@dokploy.com"
|
git config user.email "bot@dokploy.com"
|
||||||
|
|
||||||
|
|||||||
@@ -666,7 +666,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel>Custom Entrypoint</FormLabel>
|
<FormLabel>Custom Entrypoint</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Use custom entrypoint for domina
|
Use custom entrypoint for domain
|
||||||
<br />
|
<br />
|
||||||
"web" and/or "websecure" is used by default.
|
"web" and/or "websecure" is used by default.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
|
|||||||
291
apps/dokploy/components/dashboard/home/show-home.tsx
Normal file
291
apps/dokploy/components/dashboard/home/show-home.tsx
Normal file
@@ -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<string, string> = {
|
||||||
|
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 (
|
||||||
|
<div className="rounded-xl border bg-background p-5 min-h-[140px] flex flex-col justify-between">
|
||||||
|
<span className="text-xs uppercase tracking-wider text-muted-foreground">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span className="text-3xl font-semibold tracking-tight">{value}</span>
|
||||||
|
{delta && (
|
||||||
|
<span className="text-xs text-muted-foreground">{delta}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatusListCard({
|
||||||
|
label,
|
||||||
|
items,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
items: { dotClass: string; label: string; count: number }[];
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="rounded-xl border bg-background p-5 min-h-[140px] flex flex-col gap-3">
|
||||||
|
<span className="text-xs uppercase tracking-wider text-muted-foreground">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<ul className="flex flex-col gap-1.5">
|
||||||
|
{items.map((item) => (
|
||||||
|
<li key={item.label} className="flex items-center gap-2.5 text-sm">
|
||||||
|
<span
|
||||||
|
className={`size-2 rounded-full shrink-0 ${item.dotClass}`}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
<span className="font-semibold tabular-nums w-8">{item.count}</span>
|
||||||
|
<span className="text-muted-foreground">{item.label}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<typeof deployments> = [];
|
||||||
|
const prev: NonNullable<typeof deployments> = [];
|
||||||
|
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 (
|
||||||
|
<div className="w-full">
|
||||||
|
<Card className="h-full bg-sidebar p-2.5 rounded-xl min-h-[85vh]">
|
||||||
|
<div className="rounded-xl bg-background shadow-md p-6 flex flex-col gap-6 h-full">
|
||||||
|
<div className="flex flex-col gap-6 sm:flex-row sm:items-end sm:justify-between">
|
||||||
|
<h1 className="text-3xl font-semibold tracking-tight">
|
||||||
|
{firstName ? `Welcome back, ${firstName}` : "Welcome back"}
|
||||||
|
</h1>
|
||||||
|
<Button asChild variant="secondary" className="w-fit">
|
||||||
|
<Link href="/dashboard/projects">
|
||||||
|
Go to projects
|
||||||
|
<ArrowRight className="size-4" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
<StatCard
|
||||||
|
label="Projects"
|
||||||
|
value={String(totals.projects)}
|
||||||
|
delta={`${totals.environments} ${totals.environments === 1 ? "environment" : "environments"}`}
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
label="Services"
|
||||||
|
value={String(totals.services)}
|
||||||
|
delta={`${totals.applications} apps · ${totals.compose} compose · ${totals.databases} db`}
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
label="Deploys / 7d"
|
||||||
|
value={deployStats.value}
|
||||||
|
delta={deployStats.delta}
|
||||||
|
/>
|
||||||
|
<StatusListCard
|
||||||
|
label="Status"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
dotClass: "bg-emerald-500",
|
||||||
|
label: "running",
|
||||||
|
count: statusBreakdown.running,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dotClass: "bg-red-500",
|
||||||
|
label: "errored",
|
||||||
|
count: statusBreakdown.error,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dotClass: "bg-muted-foreground/40",
|
||||||
|
label: "idle",
|
||||||
|
count: statusBreakdown.idle,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-xl border bg-background">
|
||||||
|
<div className="flex items-center justify-between px-5 py-4 border-b">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Rocket className="size-4 text-muted-foreground" />
|
||||||
|
<h2 className="text-sm font-semibold">Recent deployments</h2>
|
||||||
|
</div>
|
||||||
|
{canReadDeployments && (
|
||||||
|
<Link
|
||||||
|
href="/dashboard/deployments"
|
||||||
|
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
view all →
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!canReadDeployments ? (
|
||||||
|
<div className="min-h-[400px] flex flex-col items-center justify-center gap-3 text-center text-sm text-muted-foreground p-10">
|
||||||
|
<Rocket className="size-8 opacity-40" />
|
||||||
|
<span>You do not have permission to view deployments.</span>
|
||||||
|
</div>
|
||||||
|
) : recentDeployments.length === 0 ? (
|
||||||
|
<div className="min-h-[400px] flex flex-col items-center justify-center gap-3 text-center text-sm text-muted-foreground p-10">
|
||||||
|
<Rocket className="size-8 opacity-40" />
|
||||||
|
<span>No deployments yet.</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ul className="divide-y">
|
||||||
|
{recentDeployments.map((d) => {
|
||||||
|
const info = getServiceInfo(d);
|
||||||
|
if (!info) return null;
|
||||||
|
const status = (d.status ?? "idle") as DeploymentStatus;
|
||||||
|
return (
|
||||||
|
<li key={d.deploymentId}>
|
||||||
|
<Link
|
||||||
|
href={info.href}
|
||||||
|
className="flex items-center gap-4 px-5 py-4 hover:bg-muted/40 transition-colors"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`size-2 rounded-full shrink-0 ${statusDotClass[status] ?? statusDotClass.idle}`}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col min-w-0 flex-1">
|
||||||
|
<span className="text-sm truncate">{info.name}</span>
|
||||||
|
<span className="text-xs text-muted-foreground truncate">
|
||||||
|
{info.projectName} · {info.environment}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-muted-foreground w-36 hidden lg:flex items-center justify-end gap-1.5 truncate">
|
||||||
|
<Server className="size-3 shrink-0" />
|
||||||
|
<span className="truncate">{info.serverName}</span>
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-muted-foreground w-20 text-right hidden sm:inline">
|
||||||
|
{status}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-muted-foreground w-24 text-right hidden md:inline">
|
||||||
|
{formatDistanceToNow(new Date(d.createdAt), {
|
||||||
|
addSuffix: true,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-muted-foreground hover:text-foreground transition-colors">
|
||||||
|
logs →
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -166,6 +166,7 @@ export const ShowProjects = () => {
|
|||||||
return (
|
return (
|
||||||
total +
|
total +
|
||||||
(env.applications?.length || 0) +
|
(env.applications?.length || 0) +
|
||||||
|
(env.libsql?.length || 0) +
|
||||||
(env.mariadb?.length || 0) +
|
(env.mariadb?.length || 0) +
|
||||||
(env.mongo?.length || 0) +
|
(env.mongo?.length || 0) +
|
||||||
(env.mysql?.length || 0) +
|
(env.mysql?.length || 0) +
|
||||||
@@ -178,6 +179,7 @@ export const ShowProjects = () => {
|
|||||||
return (
|
return (
|
||||||
total +
|
total +
|
||||||
(env.applications?.length || 0) +
|
(env.applications?.length || 0) +
|
||||||
|
(env.libsql?.length || 0) +
|
||||||
(env.mariadb?.length || 0) +
|
(env.mariadb?.length || 0) +
|
||||||
(env.mongo?.length || 0) +
|
(env.mongo?.length || 0) +
|
||||||
(env.mysql?.length || 0) +
|
(env.mysql?.length || 0) +
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export const SearchCommand = () => {
|
|||||||
<CommandGroup heading={"Application"} hidden={true}>
|
<CommandGroup heading={"Application"} hidden={true}>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/home");
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ export const WelcomeSubscription = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (stepper.isLast) {
|
if (stepper.isLast) {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
push("/dashboard/projects");
|
push("/dashboard/home");
|
||||||
} else {
|
} else {
|
||||||
stepper.next();
|
stepper.next();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
Forward,
|
Forward,
|
||||||
GalleryVerticalEnd,
|
GalleryVerticalEnd,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
|
House,
|
||||||
Key,
|
Key,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
Loader2,
|
Loader2,
|
||||||
@@ -148,6 +149,12 @@ type Menu = {
|
|||||||
// The `isEnabled` function is called to determine if the item should be displayed
|
// The `isEnabled` function is called to determine if the item should be displayed
|
||||||
const MENU: Menu = {
|
const MENU: Menu = {
|
||||||
home: [
|
home: [
|
||||||
|
{
|
||||||
|
isSingle: true,
|
||||||
|
title: "Home",
|
||||||
|
url: "/dashboard/home",
|
||||||
|
icon: House,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
isSingle: true,
|
isSingle: true,
|
||||||
title: "Projects",
|
title: "Projects",
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export const UserNav = () => {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/home");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Projects
|
Projects
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function SignInWithSSO({ children }: SignInWithSSOProps) {
|
|||||||
try {
|
try {
|
||||||
const { data, error } = await authClient.signIn.sso({
|
const { data, error } = await authClient.signIn.sso({
|
||||||
email: values.email,
|
email: values.email,
|
||||||
callbackURL: "/dashboard/projects",
|
callbackURL: "/dashboard/home",
|
||||||
});
|
});
|
||||||
if (error) {
|
if (error) {
|
||||||
toast.error(error.message ?? "Failed to sign in with SSO");
|
toast.error(error.message ?? "Failed to sign in with SSO");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.29.0",
|
"version": "v0.29.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default function Custom404({ statusCode, error }: Props) {
|
|||||||
|
|
||||||
<div className="mt-5 flex flex-col justify-center items-center gap-2 sm:flex-row sm:gap-3">
|
<div className="mt-5 flex flex-col justify-center items-center gap-2 sm:flex-row sm:gap-3">
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard/projects"
|
href="/dashboard/home"
|
||||||
className={buttonVariants({
|
className={buttonVariants({
|
||||||
variant: "secondary",
|
variant: "secondary",
|
||||||
className: "flex flex-row gap-2",
|
className: "flex flex-row gap-2",
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
53
apps/dokploy/pages/dashboard/home.tsx
Normal file
53
apps/dokploy/pages/dashboard/home.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||||
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
|
import type { GetServerSidePropsContext } from "next";
|
||||||
|
import type { ReactElement } from "react";
|
||||||
|
import superjson from "superjson";
|
||||||
|
import { ShowHome } from "@/components/dashboard/home/show-home";
|
||||||
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
|
import { appRouter } from "@/server/api/root";
|
||||||
|
|
||||||
|
const Home = () => {
|
||||||
|
return <ShowHome />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
||||||
|
|
||||||
|
Home.getLayout = (page: ReactElement) => {
|
||||||
|
return <DashboardLayout>{page}</DashboardLayout>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||||
|
const { req, res } = ctx;
|
||||||
|
const { user, session } = await validateRequest(req);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: true,
|
||||||
|
destination: "/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const helpers = createServerSideHelpers({
|
||||||
|
router: appRouter,
|
||||||
|
ctx: {
|
||||||
|
req: req as any,
|
||||||
|
res: res as any,
|
||||||
|
db: null as any,
|
||||||
|
session: session as any,
|
||||||
|
user: user as any,
|
||||||
|
},
|
||||||
|
transformer: superjson,
|
||||||
|
});
|
||||||
|
|
||||||
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
await helpers.user.get.prefetch();
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
trpcState: helpers.dehydrate(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -96,7 +96,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -509,6 +509,14 @@ const EnvironmentPage = (
|
|||||||
deploy: api.mongo.deploy.useMutation(),
|
deploy: api.mongo.deploy.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const libsqlActions = {
|
||||||
|
start: api.libsql.start.useMutation(),
|
||||||
|
stop: api.libsql.stop.useMutation(),
|
||||||
|
move: api.libsql.move.useMutation(),
|
||||||
|
delete: api.libsql.remove.useMutation(),
|
||||||
|
deploy: api.libsql.deploy.useMutation(),
|
||||||
|
};
|
||||||
|
|
||||||
const handleBulkStart = async () => {
|
const handleBulkStart = async () => {
|
||||||
let success = 0;
|
let success = 0;
|
||||||
setIsBulkActionLoading(true);
|
setIsBulkActionLoading(true);
|
||||||
@@ -541,6 +549,9 @@ const EnvironmentPage = (
|
|||||||
case "mongo":
|
case "mongo":
|
||||||
await mongoActions.start.mutateAsync({ mongoId: serviceId });
|
await mongoActions.start.mutateAsync({ mongoId: serviceId });
|
||||||
break;
|
break;
|
||||||
|
case "libsql":
|
||||||
|
await libsqlActions.start.mutateAsync({ libsqlId: serviceId });
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
success++;
|
success++;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -588,6 +599,9 @@ const EnvironmentPage = (
|
|||||||
case "mongo":
|
case "mongo":
|
||||||
await mongoActions.stop.mutateAsync({ mongoId: serviceId });
|
await mongoActions.stop.mutateAsync({ mongoId: serviceId });
|
||||||
break;
|
break;
|
||||||
|
case "libsql":
|
||||||
|
await libsqlActions.stop.mutateAsync({ libsqlId: serviceId });
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
success++;
|
success++;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -664,6 +678,12 @@ const EnvironmentPage = (
|
|||||||
targetEnvironmentId: selectedTargetEnvironment,
|
targetEnvironmentId: selectedTargetEnvironment,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "libsql":
|
||||||
|
await libsqlActions.move.mutateAsync({
|
||||||
|
libsqlId: serviceId,
|
||||||
|
targetEnvironmentId: selectedTargetEnvironment,
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
await utils.environment.one.invalidate({
|
await utils.environment.one.invalidate({
|
||||||
environmentId,
|
environmentId,
|
||||||
@@ -733,6 +753,11 @@ const EnvironmentPage = (
|
|||||||
mongoId: serviceId,
|
mongoId: serviceId,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "libsql":
|
||||||
|
await libsqlActions.delete.mutateAsync({
|
||||||
|
libsqlId: serviceId,
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
await utils.environment.one.invalidate({
|
await utils.environment.one.invalidate({
|
||||||
environmentId,
|
environmentId,
|
||||||
@@ -799,6 +824,11 @@ const EnvironmentPage = (
|
|||||||
mongoId: serviceId,
|
mongoId: serviceId,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "libsql":
|
||||||
|
await libsqlActions.deploy.mutateAsync({
|
||||||
|
libsqlId: serviceId,
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
success++;
|
success++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1856,7 +1886,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -490,7 +490,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,7 +376,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: false,
|
permanent: false,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export async function getServerSideProps(
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toast.success("Logged in successfully");
|
toast.success("Logged in successfully");
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/home");
|
||||||
} catch {
|
} catch {
|
||||||
toast.error("An error occurred while logging in");
|
toast.error("An error occurred while logging in");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -133,7 +133,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toast.success("Logged in successfully");
|
toast.success("Logged in successfully");
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/home");
|
||||||
} catch {
|
} catch {
|
||||||
toast.error("An error occurred while verifying 2FA code");
|
toast.error("An error occurred while verifying 2FA code");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -163,7 +163,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toast.success("Logged in successfully");
|
toast.success("Logged in successfully");
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/home");
|
||||||
} catch {
|
} catch {
|
||||||
toast.error("An error occurred while verifying backup code");
|
toast.error("An error occurred while verifying backup code");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -408,7 +408,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -437,7 +437,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ const Invitation = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
toast.success("Account created successfully");
|
toast.success("Account created successfully");
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/home");
|
||||||
} catch {
|
} catch {
|
||||||
toast.error("An error occurred while creating your account");
|
toast.error("An error occurred while creating your account");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: true,
|
||||||
destination: "/dashboard/projects",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -487,6 +487,148 @@ export const projectRouter = createTRPCRouter({
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
homeStats: protectedProcedure.query(async ({ ctx }) => {
|
||||||
|
const isPrivileged = ctx.user.role === "owner" || ctx.user.role === "admin";
|
||||||
|
|
||||||
|
let accessedProjects: string[] = [];
|
||||||
|
let accessedEnvironments: string[] = [];
|
||||||
|
let accessedServices: string[] = [];
|
||||||
|
|
||||||
|
if (!isPrivileged) {
|
||||||
|
const member = await findMemberByUserId(
|
||||||
|
ctx.user.id,
|
||||||
|
ctx.session.activeOrganizationId,
|
||||||
|
);
|
||||||
|
accessedProjects = member.accessedProjects;
|
||||||
|
accessedEnvironments = member.accessedEnvironments;
|
||||||
|
accessedServices = member.accessedServices;
|
||||||
|
|
||||||
|
if (accessedProjects.length === 0) {
|
||||||
|
return {
|
||||||
|
projects: 0,
|
||||||
|
environments: 0,
|
||||||
|
applications: 0,
|
||||||
|
compose: 0,
|
||||||
|
databases: 0,
|
||||||
|
services: 0,
|
||||||
|
status: { running: 0, error: 0, idle: 0 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectIdFilter = isPrivileged
|
||||||
|
? eq(projects.organizationId, ctx.session.activeOrganizationId)
|
||||||
|
: and(
|
||||||
|
sql`${projects.projectId} IN (${sql.join(
|
||||||
|
accessedProjects.map((id) => sql`${id}`),
|
||||||
|
sql`, `,
|
||||||
|
)})`,
|
||||||
|
eq(projects.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const environmentFilter = isPrivileged
|
||||||
|
? undefined
|
||||||
|
: accessedEnvironments.length === 0
|
||||||
|
? sql`false`
|
||||||
|
: sql`${environments.environmentId} IN (${sql.join(
|
||||||
|
accessedEnvironments.map((envId) => sql`${envId}`),
|
||||||
|
sql`, `,
|
||||||
|
)})`;
|
||||||
|
|
||||||
|
const applyFilter = (col: AnyPgColumn) =>
|
||||||
|
isPrivileged ? undefined : buildServiceFilter(col, accessedServices);
|
||||||
|
|
||||||
|
const rows = await db.query.projects.findMany({
|
||||||
|
where: projectIdFilter,
|
||||||
|
columns: { projectId: true },
|
||||||
|
with: {
|
||||||
|
environments: {
|
||||||
|
where: environmentFilter,
|
||||||
|
columns: { environmentId: true },
|
||||||
|
with: {
|
||||||
|
applications: {
|
||||||
|
where: applyFilter(applications.applicationId),
|
||||||
|
columns: { applicationStatus: true },
|
||||||
|
},
|
||||||
|
compose: {
|
||||||
|
where: applyFilter(compose.composeId),
|
||||||
|
columns: { composeStatus: true },
|
||||||
|
},
|
||||||
|
libsql: {
|
||||||
|
where: applyFilter(libsql.libsqlId),
|
||||||
|
columns: { applicationStatus: true },
|
||||||
|
},
|
||||||
|
mariadb: {
|
||||||
|
where: applyFilter(mariadb.mariadbId),
|
||||||
|
columns: { applicationStatus: true },
|
||||||
|
},
|
||||||
|
mongo: {
|
||||||
|
where: applyFilter(mongo.mongoId),
|
||||||
|
columns: { applicationStatus: true },
|
||||||
|
},
|
||||||
|
mysql: {
|
||||||
|
where: applyFilter(mysql.mysqlId),
|
||||||
|
columns: { applicationStatus: true },
|
||||||
|
},
|
||||||
|
postgres: {
|
||||||
|
where: applyFilter(postgres.postgresId),
|
||||||
|
columns: { applicationStatus: true },
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
where: applyFilter(redis.redisId),
|
||||||
|
columns: { applicationStatus: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let applicationsCount = 0;
|
||||||
|
let composeCount = 0;
|
||||||
|
let databasesCount = 0;
|
||||||
|
let environmentsCount = 0;
|
||||||
|
const status = { running: 0, error: 0, idle: 0 };
|
||||||
|
const bump = (s?: string | null) => {
|
||||||
|
if (s === "done") status.running++;
|
||||||
|
else if (s === "error") status.error++;
|
||||||
|
else status.idle++;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const project of rows) {
|
||||||
|
for (const env of project.environments) {
|
||||||
|
environmentsCount++;
|
||||||
|
applicationsCount += env.applications.length;
|
||||||
|
composeCount += env.compose.length;
|
||||||
|
databasesCount +=
|
||||||
|
env.libsql.length +
|
||||||
|
env.mariadb.length +
|
||||||
|
env.mongo.length +
|
||||||
|
env.mysql.length +
|
||||||
|
env.postgres.length +
|
||||||
|
env.redis.length;
|
||||||
|
|
||||||
|
for (const a of env.applications) bump(a.applicationStatus);
|
||||||
|
for (const c of env.compose) bump(c.composeStatus);
|
||||||
|
for (const s of env.libsql) bump(s.applicationStatus);
|
||||||
|
for (const s of env.mariadb) bump(s.applicationStatus);
|
||||||
|
for (const s of env.mongo) bump(s.applicationStatus);
|
||||||
|
for (const s of env.mysql) bump(s.applicationStatus);
|
||||||
|
for (const s of env.postgres) bump(s.applicationStatus);
|
||||||
|
for (const s of env.redis) bump(s.applicationStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
projects: rows.length,
|
||||||
|
environments: environmentsCount,
|
||||||
|
applications: applicationsCount,
|
||||||
|
compose: composeCount,
|
||||||
|
databases: databasesCount,
|
||||||
|
services: applicationsCount + composeCount + databasesCount,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
search: protectedProcedure
|
search: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
|||||||
@@ -30,13 +30,9 @@ export const findPreviewDeploymentById = async (
|
|||||||
with: {
|
with: {
|
||||||
domain: true,
|
domain: true,
|
||||||
application: {
|
application: {
|
||||||
with: {
|
columns: {
|
||||||
server: true,
|
applicationId: true,
|
||||||
environment: {
|
serverId: true,
|
||||||
with: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user