Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/remove build on server #591

Merged
merged 44 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
988357f
refactor: update payments
Siumauricio Oct 18, 2024
e32b713
Merge branch 'canary' into feat/payments
Siumauricio Oct 18, 2024
2d40b2d
feat: add stripe integration
Siumauricio Oct 18, 2024
319584d
Merge branch 'canary' into feat/payments
Siumauricio Oct 19, 2024
fe0a662
feat(cloud): add billing wip
Siumauricio Oct 20, 2024
ffe7b04
feat: add stripe webhooks
Siumauricio Oct 20, 2024
1907e7e
feat: add webhook
Siumauricio Oct 21, 2024
fbda00f
refactor: update webhooks and added validation to prevent deploy when…
Siumauricio Oct 21, 2024
9591fbf
refactor: add Is cloud flag
Siumauricio Oct 21, 2024
626cfb8
refactor: add website url redirect
Siumauricio Oct 21, 2024
4685ef7
refactor: update stripe envs
Siumauricio Oct 21, 2024
03f923c
refactor: update show servers
Siumauricio Oct 21, 2024
255e9e4
refactor: remove dokploy restart on notifications
Siumauricio Oct 21, 2024
53edf06
refactor: remove comments
Siumauricio Oct 21, 2024
c0afcaf
refactor: show banner when the server is disabled
Siumauricio Oct 21, 2024
9bace8e
refactor: clean stripe customer if the customer is deleted
Siumauricio Oct 21, 2024
49ee8ce
Merge branch 'canary' into feat/payments
Siumauricio Oct 22, 2024
22e42b6
feat: add is cloud in ssr
Siumauricio Oct 22, 2024
431dadb
feat: add pricing
Siumauricio Oct 22, 2024
1e6dbb5
feat(dokploy): add reset password for cloud
Siumauricio Oct 22, 2024
548df8c
Merge branch 'canary' into feat/payments
Siumauricio Oct 23, 2024
017bdd2
refactor(dokploy): add flag to prevent run commands when is cloud
Siumauricio Oct 23, 2024
975d13c
refactor(dokploy): disable stats monitoring
Siumauricio Oct 23, 2024
c1f777e
refactor: remove serverIp
Siumauricio Oct 23, 2024
7e76eb4
refactor: delete log
Siumauricio Oct 23, 2024
dfdedf9
chore(dokploy): simplify migrations
Siumauricio Oct 23, 2024
6c7c919
refactor: set servers quantity in 0 when the subscription is created
Siumauricio Oct 24, 2024
e15d41f
chore: add radix tabs
Siumauricio Oct 24, 2024
647a5d0
test(dokploy): add missing fields
Siumauricio Oct 24, 2024
bcc7afa
refactor(dokploy): fix ts errors
Siumauricio Oct 24, 2024
fa053b4
refactor(dokploy): remove stripe from global scope
Siumauricio Oct 24, 2024
cb586c9
Merge branch 'canary' into feat/payments
Siumauricio Oct 24, 2024
47aa223
refactor: remove save on build on next app and integrate turbopack
Siumauricio Oct 25, 2024
c42f5cb
refactor: update
Siumauricio Oct 25, 2024
4911134
chore: remove server build
Siumauricio Oct 25, 2024
fbada4c
chore: set right filter pnpm dockerfile
Siumauricio Oct 25, 2024
484ead1
chore: update workflows
Siumauricio Oct 25, 2024
054836f
chore: update workflows
Siumauricio Oct 25, 2024
5df7654
chore: update imports
Siumauricio Oct 25, 2024
7f94593
chore: revert ci/cd
Siumauricio Oct 25, 2024
af3b1a2
refactor: update tests
Siumauricio Oct 25, 2024
83d52b6
refactor: update
Siumauricio Oct 25, 2024
60d4e1b
chore: update dockerfiles
Siumauricio Oct 25, 2024
303d1b1
chore: add missing command
Siumauricio Oct 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ Run the command that will spin up all the required services and files.
pnpm run dokploy:setup
```

Build the server package (If you make any changes after in the packages/server folder, you need to rebuild and run this command)

Run this script
```bash
pnpm run server:build
pnpm run server:script
```

Now run the development server.
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
# Deploy only the dokploy app

ENV NODE_ENV=production
RUN pnpm --filter=@dokploy/server switch:prod
RUN pnpm --filter=@dokploy/server build
RUN pnpm --filter=./apps/dokploy run build

Expand Down
1 change: 1 addition & 0 deletions Dockerfile.cloud
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server
# Deploy only the dokploy app

ENV NODE_ENV=production
RUN pnpm --filter=@dokploy/server switch:prod
RUN pnpm --filter=@dokploy/server build
RUN pnpm --filter=./apps/dokploy run build

Expand Down
1 change: 1 addition & 0 deletions Dockerfile.schedule
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server
# Deploy only the dokploy app

ENV NODE_ENV=production
RUN pnpm --filter=@dokploy/server switch:prod
RUN pnpm --filter=@dokploy/server build
RUN pnpm --filter=./apps/schedules run build

Expand Down
1 change: 1 addition & 0 deletions Dockerfile.server
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server
# Deploy only the dokploy app

ENV NODE_ENV=production
RUN pnpm --filter=@dokploy/server switch:prod
RUN pnpm --filter=@dokploy/server build
RUN pnpm --filter=./apps/api run build

Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
server: z.boolean().optional(),
type: z.enum(["deploy", "redeploy"]),
applicationType: z.literal("application"),
serverId: z.string(),
serverId: z.string().min(1),
}),
z.object({
composeId: z.string(),
Expand All @@ -17,7 +17,7 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
server: z.boolean().optional(),
type: z.enum(["deploy", "redeploy"]),
applicationType: z.literal("compose"),
serverId: z.string(),
serverId: z.string().min(1),
}),
]);

Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
rebuildRemoteCompose,
updateApplicationStatus,
updateCompose,
} from "@dokploy/server";
} from "@dokploy/server/dist";
import type { DeployJob } from "./schema";
import type { LemonSqueezyLicenseResponse } from "./types";

Expand Down
23 changes: 12 additions & 11 deletions apps/dokploy/__test__/drop/drop.test.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import fs from "node:fs/promises";
import path from "node:path";
import { paths } from "@dokploy/server/dist/constants";
import { paths } from "@dokploy/server/constants";
const { APPLICATIONS_PATH } = paths();
import type { ApplicationNested } from "@dokploy/server";
import { unzipDrop } from "@dokploy/server";
import AdmZip from "adm-zip";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";

vi.mock("@dokploy/server/constants", async (importOriginal) => {
const actual = await importOriginal();
return {
// @ts-ignore
...actual,
paths: () => ({
APPLICATIONS_PATH: "./__test__/drop/zips/output",
}),
};
});

if (typeof window === "undefined") {
const undici = require("undici");
globalThis.File = undici.File as any;
Expand Down Expand Up @@ -82,16 +93,6 @@ const baseApp: ApplicationNested = {
dockerContextPath: null,
};

vi.mock("@dokploy/server/dist/constants", async (importOriginal) => {
const actual = await importOriginal();
return {
// @ts-ignore
...actual,
paths: () => ({
APPLICATIONS_PATH: "./__test__/drop/zips/output",
}),
};
});
describe("unzipDrop using real zip files", () => {
// const { APPLICATIONS_PATH } = paths();
beforeAll(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const baseAdmin: Admin = {
sshPrivateKey: null,
enableDockerCleanup: false,
enableLogRotation: false,
serversQuantity: 0,
stripeCustomerId: "",
stripeSubscriptionId: "",
};

beforeEach(() => {
Expand Down
15 changes: 9 additions & 6 deletions apps/dokploy/__test__/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import path from "node:path";
import tsconfigPaths from "vite-tsconfig-paths";
import { defineConfig } from "vitest/config";

export default defineConfig({
plugins: [
tsconfigPaths({
root: "./",
projects: ["tsconfig.json"],
}),
],
test: {
include: ["__test__/**/*.test.ts"], // Incluir solo los archivos de test en el directorio __test__
exclude: ["**/node_modules/**", "**/dist/**", "**/.docker/**"],
Expand All @@ -18,4 +13,12 @@ export default defineConfig({
NODE: "test",
},
},
resolve: {
alias: {
"@dokploy/server": path.resolve(
__dirname,
"../../../packages/server/src",
),
},
},
});
241 changes: 241 additions & 0 deletions apps/dokploy/components/dashboard/settings/billing/show-billing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import { Button } from "@/components/ui/button";
import { NumberInput } from "@/components/ui/input";
import { Progress } from "@/components/ui/progress";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { loadStripe } from "@stripe/stripe-js";
import clsx from "clsx";
import { AlertTriangle, CheckIcon, MinusIcon, PlusIcon } from "lucide-react";
import React, { useState } from "react";

const stripePromise = loadStripe(
"pk_test_51QAm7bF3cxQuHeOz0xg04o9teeyTbbNHQPJ5Tr98MlTEan9MzewT3gwh0jSWBNvrRWZ5vASoBgxUSF4gPWsJwATk00Ir2JZ0S1",
);

export const calculatePrice = (count: number, isAnnual = false) => {
if (isAnnual) {
if (count <= 1) return 45.9;
return 35.7 * count;
}
if (count <= 1) return 4.5;
return count * 3.5;
};
// 178.156.147.118
export const ShowBilling = () => {
const { data: servers } = api.server.all.useQuery(undefined);
const { data: admin } = api.admin.one.useQuery();
const { data } = api.stripe.getProducts.useQuery();
const { mutateAsync: createCheckoutSession } =
api.stripe.createCheckoutSession.useMutation();

const { mutateAsync: createCustomerPortalSession } =
api.stripe.createCustomerPortalSession.useMutation();

const [serverQuantity, setServerQuantity] = useState(3);
const [isAnnual, setIsAnnual] = useState(false);

const handleCheckout = async (productId: string) => {
const stripe = await stripePromise;
if (data && data.subscriptions.length === 0) {
createCheckoutSession({
productId,
serverQuantity: serverQuantity,
isAnnual,
}).then(async (session) => {
await stripe?.redirectToCheckout({
sessionId: session.sessionId,
});
});
}
};
const products = data?.products.filter((product) => {
// @ts-ignore
const interval = product?.default_price?.recurring?.interval;
return isAnnual ? interval === "year" : interval === "month";
});

const maxServers = admin?.serversQuantity ?? 1;
const percentage = ((servers?.length ?? 0) / maxServers) * 100;
const safePercentage = Math.min(percentage, 100);

return (
<div className="flex flex-col gap-4 w-full">
<Tabs
defaultValue="monthly"
value={isAnnual ? "annual" : "monthly"}
className="w-full"
onValueChange={(e) => setIsAnnual(e === "annual")}
>
<TabsList>
<TabsTrigger value="monthly">Monthly</TabsTrigger>
<TabsTrigger value="annual">Annual</TabsTrigger>
</TabsList>
</Tabs>
{admin?.stripeSubscriptionId && (
<div className="space-y-2">
<h3 className="text-lg font-medium">Servers Plan</h3>
<p className="text-sm text-muted-foreground">
You have {servers?.length} server on your plan of{" "}
{admin?.serversQuantity} servers
</p>
<div className="pb-5">
<Progress value={safePercentage} className="max-w-lg" />
</div>
{admin && (
<>
{admin.serversQuantity! <= servers?.length! && (
<div className="flex flex-row gap-4 p-2 bg-yellow-50 dark:bg-yellow-950 rounded-lg items-center">
<AlertTriangle className="text-yellow-600 dark:text-yellow-400" />
<span className="text-sm text-yellow-600 dark:text-yellow-400">
You have reached the maximum number of servers you can
create, please upgrade your plan to add more servers.
</span>
</div>
)}
</>
)}
</div>
)}
{products?.map((product) => {
const featured = true;
return (
<div key={product.id}>
<section
className={clsx(
"flex flex-col rounded-3xl border-dashed border-2 px-4 max-w-sm",
featured
? "order-first bg-black border py-8 lg:order-none"
: "lg:py-8",
)}
>
{isAnnual ? (
<div className="flex flex-row gap-2 items-center">
<p className=" text-2xl font-semibold tracking-tight text-primary ">
$ {calculatePrice(serverQuantity, isAnnual).toFixed(2)} USD
</p>
|
<p className=" text-base font-semibold tracking-tight text-muted-foreground">
${" "}
{(calculatePrice(serverQuantity, isAnnual) / 12).toFixed(2)}{" "}
/ Month USD
</p>
</div>
) : (
<p className=" text-2xl font-semibold tracking-tight text-primary ">
$ {calculatePrice(serverQuantity, isAnnual).toFixed(2)} USD
</p>
)}
<h3 className="mt-5 font-medium text-lg text-white">
{product.name}
</h3>
<p
className={clsx(
"text-sm",
featured ? "text-white" : "text-slate-400",
)}
>
{product.description}
</p>

<ul
role="list"
className={clsx(
" mt-4 flex flex-col gap-y-2 text-sm",
featured ? "text-white" : "text-slate-200",
)}
>
{[
"All the features of Dokploy",
"Unlimited deployments",
"Self-hosted on your own infrastructure",
"Full access to all deployment features",
"Dokploy integration",
"Backups",
"All Incoming features",
].map((feature) => (
<li key={feature} className="flex text-muted-foreground">
<CheckIcon />
<span className="ml-4">{feature}</span>
</li>
))}
</ul>
<div className="flex flex-col gap-2 mt-4">
<div className="flex items-center gap-2 justify-center">
<span className="text-sm text-muted-foreground">
{serverQuantity} Servers
</span>
</div>

<div className="flex items-center space-x-2">
<Button
disabled={serverQuantity <= 1}
variant="outline"
onClick={() => {
if (serverQuantity <= 1) return;

setServerQuantity(serverQuantity - 1);
}}
>
<MinusIcon className="h-4 w-4" />
</Button>
<NumberInput
value={serverQuantity}
onChange={(e) => {
setServerQuantity(e.target.value as unknown as number);
}}
/>

<Button
variant="outline"
onClick={() => {
setServerQuantity(serverQuantity + 1);
}}
>
<PlusIcon className="h-4 w-4" />
</Button>
</div>
<div
className={cn(
data?.subscriptions && data?.subscriptions?.length > 0
? "justify-between"
: "justify-end",
"flex flex-row items-center gap-2 mt-4",
)}
>
{admin?.stripeCustomerId && (
<Button
variant="secondary"
className="w-full"
onClick={async () => {
const session = await createCustomerPortalSession();

window.open(session.url);
}}
>
Manage Subscription
</Button>
)}

{data?.subscriptions?.length === 0 && (
<div className="justify-end w-full">
<Button
className="w-full"
onClick={async () => {
handleCheckout(product.id);
}}
disabled={serverQuantity < 1}
>
Subscribe
</Button>
</div>
)}
</div>
</div>
</section>
</div>
);
})}
</div>
);
};
Loading