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 && (
-
-
-
- License Key
-
- setLicenseKey(e.target.value)}
- />
-
-
- {
- try {
- await updateEnterpriseSettings({ licenseKey });
- await utils.licenseKey.getEnterpriseSettings.invalidate();
- toast.success("License key saved");
- } catch (error) {
- console.error(error);
- toast.error("Failed to save license key");
- }
- }}
- >
- Save
-
-
-
- )}
-
- );
-}
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}
+ >
+
+ Deactivate
+
+
+ )}
{
try {
- await updateEnterpriseSettings({ licenseKey });
- await utils.licenseKey.getEnterpriseSettings.invalidate();
- toast.success("License key saved");
+ const valid = await validateLicenseKey({ licenseKey });
+ if (valid) {
+ toast.success("License key is valid");
+ } else {
+ toast.error("License key is invalid");
+ }
} catch (error) {
console.error(error);
- toast.error("Failed to save license key");
+ toast.error(
+ error instanceof Error
+ ? error.message
+ : "Failed to validate license key",
+ );
}
}}
>
- Save
+ Validate
+ {!isValid && (
+ {
+ try {
+ await activateLicenseKey({ licenseKey });
+ await utils.licenseKey.getEnterpriseSettings.invalidate();
+ // Re-validate after saving to update the Deactivate button visibility
+ const valid = await validateLicenseKey({ licenseKey });
+ setIsValid(valid);
+ toast.success("License key activated");
+ } catch (error) {
+ console.error(error);
+ toast.error(
+ error instanceof Error
+ ? error.message
+ : "Failed to activate license key",
+ );
+ }
+ }}
+ >
+ Activate
+
+ )}
)}
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;
+ }
+};