diff --git a/apps/dokploy/components/dashboard/settings/web-server/license-key.tsx b/apps/dokploy/components/dashboard/settings/web-server/license-key.tsx deleted file mode 100644 index ed02180e9..000000000 --- a/apps/dokploy/components/dashboard/settings/web-server/license-key.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { Key } from "lucide-react"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { toast } from "sonner"; -import { Button } from "@/components/ui/button"; -import { CardTitle } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { Switch } from "@/components/ui/switch"; -import { api } from "@/utils/api"; - -export function LicenseKeySettings() { - const utils = api.useUtils(); - const { data, isLoading } = api.licenseKey.getEnterpriseSettings.useQuery(); - const { mutateAsync: updateEnterpriseSettings, isLoading: isSaving } = - api.licenseKey.updateEnterpriseSettings.useMutation(); - - const [licenseKey, setLicenseKey] = useState(""); - - useEffect(() => { - if (data?.licenseKey !== undefined) { - setLicenseKey(data.licenseKey ?? ""); - } - }, [data?.licenseKey]); - - const enabled = !!data?.enableEnterpriseFeatures; - - return ( -
-
-
-
- - License Key -
- -
- - {enabled ? "Enabled" : "Disabled"} - - { - try { - await updateEnterpriseSettings({ - enableEnterpriseFeatures: next, - }); - await utils.licenseKey.getEnterpriseSettings.invalidate(); - toast.success("Enterprise features updated"); - } catch (error) { - console.error(error); - toast.error("Failed to update enterprise features"); - } - }} - /> -
-
- -

- To unlock extra features you need an enterprise license key. Contact - us{" "} - - here - - . -

-
- - {enabled && ( -
-
- - setLicenseKey(e.target.value)} - /> -
-
- -
-
- )} -
- ); -} diff --git a/apps/dokploy/components/proprietary/license-keys/license-key.tsx b/apps/dokploy/components/proprietary/license-keys/license-key.tsx index 665c85e50..c5bf8cdff 100644 --- a/apps/dokploy/components/proprietary/license-keys/license-key.tsx +++ b/apps/dokploy/components/proprietary/license-keys/license-key.tsx @@ -2,6 +2,7 @@ import { Key } from "lucide-react"; import Link from "next/link"; import { useEffect, useState } from "react"; import { toast } from "sonner"; +import { DialogAction } from "@/components/shared/dialog-action"; import { Button } from "@/components/ui/button"; import { CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; @@ -13,14 +14,27 @@ export function LicenseKeySettings() { const { data, isLoading } = api.licenseKey.getEnterpriseSettings.useQuery(); const { mutateAsync: updateEnterpriseSettings, isLoading: isSaving } = api.licenseKey.updateEnterpriseSettings.useMutation(); + const { mutateAsync: activateLicenseKey, isLoading: isActivating } = + api.licenseKey.activate.useMutation(); + const { mutateAsync: validateLicenseKey, isLoading: isValidating } = + api.licenseKey.validate.useMutation(); + const { mutateAsync: deactivateLicenseKey, isLoading: isDeactivating } = + api.licenseKey.deactivate.useMutation(); const [licenseKey, setLicenseKey] = useState(""); + const [isValid, setIsValid] = useState(false); useEffect(() => { - if (data?.licenseKey !== undefined) { - setLicenseKey(data.licenseKey ?? ""); + if (data?.licenseKey) { + setLicenseKey(data.licenseKey); + validateLicenseKey({ licenseKey: data.licenseKey }) + .then((valid) => { + console.log("valid", valid); + setIsValid(valid); + }) + .catch(() => setIsValid(false)); } - }, [data?.licenseKey]); + }, [data?.licenseKey, validateLicenseKey]); const enabled = !!data?.enableEnterpriseFeatures; @@ -39,7 +53,7 @@ export function LicenseKeySettings() { { try { await updateEnterpriseSettings({ @@ -57,7 +71,8 @@ export function LicenseKeySettings() {

- To unlock extra features you need an enterprise license key. Contact us{" "} + To unlock extra features you need an enterprise license key. Contact + us{" "} setLicenseKey(e.target.value)} /> -

+
+ {isValid && ( + { + try { + await deactivateLicenseKey({ licenseKey }); + await utils.licenseKey.getEnterpriseSettings.invalidate(); + setIsValid(false); + toast.success("License key deactivated"); + } catch (error) { + console.error(error); + toast.error( + error instanceof Error + ? error.message + : "Failed to deactivate license key", + ); + } + }} + disabled={isDeactivating || !data?.licenseKey} + > + + + )} + {!isValid && ( + + )}
)} diff --git a/apps/dokploy/server/api/routers/proprietary/license-key.ts b/apps/dokploy/server/api/routers/proprietary/license-key.ts index 5a45719ed..ba4f514ef 100644 --- a/apps/dokploy/server/api/routers/proprietary/license-key.ts +++ b/apps/dokploy/server/api/routers/proprietary/license-key.ts @@ -4,8 +4,103 @@ import { eq } from "drizzle-orm"; import { z } from "zod"; import { adminProcedure, createTRPCRouter } from "@/server/api/trpc"; import { db } from "@/server/db"; +import { + activateLicenseKey, + deactivateLicenseKey, + validateLicenseKey, +} from "@/server/utils/enterprise"; export const licenseKeyRouter = createTRPCRouter({ + activate: adminProcedure + .input(z.object({ licenseKey: z.string() })) + .mutation(async ({ input, ctx }) => { + try { + const currentUserId = ctx.user.id; + const currentUser = await db.query.user.findFirst({ + where: eq(user.id, currentUserId), + }); + if (!currentUser) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "User not found", + }); + } + + if (!currentUser.enableEnterpriseFeatures) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + "Please activate enterprise features to activate license key", + }); + } + + return await activateLicenseKey(input.licenseKey); + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: + error instanceof Error + ? error.message + : "Failed to activate license key", + cause: error, + }); + } + }), + validate: adminProcedure + .input(z.object({ licenseKey: z.string() })) + .mutation(async ({ input, ctx }) => { + try { + const currentUserId = ctx.user.id; + const currentUser = await db.query.user.findFirst({ + where: eq(user.id, currentUserId), + }); + if (!currentUser) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "User not found", + }); + } + + if (!currentUser.enableEnterpriseFeatures) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + "Please activate enterprise features to validate license key", + }); + } + return await validateLicenseKey(input.licenseKey); + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: + error instanceof Error + ? error.message + : "Failed to validate license key", + }); + } + }), + deactivate: adminProcedure + .input(z.object({ licenseKey: z.string() })) + .mutation(async ({ input }) => { + try { + const isValidLicenseKey = await validateLicenseKey(input.licenseKey); + if (!isValidLicenseKey) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "License key is invalid", + }); + } + return await deactivateLicenseKey(input.licenseKey); + } catch (error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: + error instanceof Error + ? error.message + : "Failed to deactivate license key", + }); + } + }), getEnterpriseSettings: adminProcedure.query(async ({ ctx }) => { const currentUserId = ctx.user.id; const currentUser = await db.query.user.findFirst({ @@ -29,31 +124,35 @@ export const licenseKeyRouter = createTRPCRouter({ .input( z.object({ enableEnterpriseFeatures: z.boolean().optional(), - licenseKey: z.string().optional(), }), ) .mutation(async ({ ctx, input }) => { - const currentUserId = ctx.user.id; + try { + const currentUserId = ctx.user.id; - if ( - input.enableEnterpriseFeatures === undefined && - input.licenseKey === undefined - ) { + if (input.enableEnterpriseFeatures === undefined) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "enableEnterpriseFeatures must be provided", + }); + } + + await db + .update(user) + .set({ + enableEnterpriseFeatures: input.enableEnterpriseFeatures, + }) + .where(eq(user.id, currentUserId)); + + return true; + } catch (error) { throw new TRPCError({ - code: "BAD_REQUEST", + code: "INTERNAL_SERVER_ERROR", message: - "At least one of enableEnterpriseFeatures or licenseKey must be provided", + error instanceof Error + ? error.message + : "Failed to update enterprise settings", }); } - - await db - .update(user) - .set({ - // enableEnterpriseFeatures: input.enableEnterpriseFeatures ?? false, - licenseKey: input.licenseKey ?? "", - }) - .where(eq(user.id, currentUserId)); - - return true; }), }); diff --git a/apps/dokploy/server/utils/enterprise.ts b/apps/dokploy/server/utils/enterprise.ts new file mode 100644 index 000000000..fe7cb7ca3 --- /dev/null +++ b/apps/dokploy/server/utils/enterprise.ts @@ -0,0 +1,76 @@ +import { getPublicIpWithFallback } from "@dokploy/server/index"; + +const LICENSE_KEY_URL = process.env.LICENSE_KEY_URL || "http://localhost:4002"; + +export const validateLicenseKey = async (licenseKey: string) => { + try { + const ip = await getPublicIpWithFallback(); + const result = await fetch(`${LICENSE_KEY_URL}/licenses/validate`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ licenseKey, ip }), + }); + + if (!result.ok) { + const errorData = await result.json().catch(() => ({})); + throw new Error(errorData.message || "Failed to validate license key"); + } + + const data = await result.json(); + console.log("data", data); + return data.valid; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const activateLicenseKey = async (licenseKey: string) => { + try { + const ip = await getPublicIpWithFallback(); + const result = await fetch(`${LICENSE_KEY_URL}/licenses/activate`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ licenseKey, ip }), + }); + + if (!result.ok) { + const errorData = await result.json().catch(() => ({})); + throw new Error(errorData.message || "Failed to activate license key"); + } + + const data = await result.json(); + return data; + } catch (error) { + console.error(error); + throw error; + } +}; + +export const deactivateLicenseKey = async (licenseKey: string) => { + try { + const ip = await getPublicIpWithFallback(); + const result = await fetch(`${LICENSE_KEY_URL}/licenses/deactivate`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ licenseKey, ip }), + }); + + if (!result.ok) { + const errorData = await result.json().catch(() => ({})); + throw new Error(errorData.message || "Failed to deactivate license key"); + } + + const data = await result.json(); + return data; + } catch (error) { + console.error(error); + throw error; + } +};