From 25fa362cdb969ad67df14aa4dbe814312191fb51 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Wed, 28 Jan 2026 22:34:17 -0600 Subject: [PATCH] Add enterprise features management: implement license key settings and update user schema --- .../proprietary/license-keys/license-key.tsx | 108 ++++++++++++++++++ .../pages/dashboard/settings/server.tsx | 2 +- apps/dokploy/server/api/root.ts | 2 + .../api/routers/proprietary/license-key.ts | 52 +++++++++ 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 apps/dokploy/components/proprietary/license-keys/license-key.tsx create mode 100644 apps/dokploy/server/api/routers/proprietary/license-key.ts diff --git a/apps/dokploy/components/proprietary/license-keys/license-key.tsx b/apps/dokploy/components/proprietary/license-keys/license-key.tsx new file mode 100644 index 000000000..665c85e50 --- /dev/null +++ b/apps/dokploy/components/proprietary/license-keys/license-key.tsx @@ -0,0 +1,108 @@ +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/pages/dashboard/settings/server.tsx b/apps/dokploy/pages/dashboard/settings/server.tsx index 009d1d2bc..7de3578d5 100644 --- a/apps/dokploy/pages/dashboard/settings/server.tsx +++ b/apps/dokploy/pages/dashboard/settings/server.tsx @@ -6,8 +6,8 @@ import superjson from "superjson"; import { ShowBackups } from "@/components/dashboard/database/backups/show-backups"; import { WebDomain } from "@/components/dashboard/settings/web-domain"; import { WebServer } from "@/components/dashboard/settings/web-server"; -import { LicenseKeySettings } from "@/components/dashboard/settings/web-server/license-key"; import { DashboardLayout } from "@/components/layouts/dashboard-layout"; +import { LicenseKeySettings } from "@/components/proprietary/license-keys/license-key"; import { Card } from "@/components/ui/card"; import { appRouter } from "@/server/api/root"; import { api } from "@/utils/api"; diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts index 63ce38d10..12a021ff7 100644 --- a/apps/dokploy/server/api/root.ts +++ b/apps/dokploy/server/api/root.ts @@ -22,6 +22,7 @@ import { mountRouter } from "./routers/mount"; import { mysqlRouter } from "./routers/mysql"; import { notificationRouter } from "./routers/notification"; import { organizationRouter } from "./routers/organization"; +import { licenseKeyRouter } from "./routers/proprietary/license-key"; import { portRouter } from "./routers/port"; import { postgresRouter } from "./routers/postgres"; import { previewDeploymentRouter } from "./routers/preview-deployment"; @@ -82,6 +83,7 @@ export const appRouter = createTRPCRouter({ swarm: swarmRouter, ai: aiRouter, organization: organizationRouter, + licenseKey: licenseKeyRouter, schedule: scheduleRouter, rollback: rollbackRouter, volumeBackups: volumeBackupsRouter, diff --git a/apps/dokploy/server/api/routers/proprietary/license-key.ts b/apps/dokploy/server/api/routers/proprietary/license-key.ts new file mode 100644 index 000000000..7ec2ff2c6 --- /dev/null +++ b/apps/dokploy/server/api/routers/proprietary/license-key.ts @@ -0,0 +1,52 @@ +import { user } from "@dokploy/server/db/schema"; +import { TRPCError } from "@trpc/server"; +import { eq } from "drizzle-orm"; +import { z } from "zod"; +import { adminProcedure, createTRPCRouter } from "@/server/api/trpc"; +import { db } from "@/server/db"; + +export const licenseKeyRouter = createTRPCRouter({ + getEnterpriseSettings: adminProcedure.query(async ({ ctx }) => { + 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", + }); + } + + return { + enableEnterpriseFeatures: !!currentUser.enableEnterpriseFeatures, + licenseKey: currentUser.licenseKey ?? "", + }; + }), + + updateEnterpriseSettings: adminProcedure + .input( + z.object({ + enableEnterpriseFeatures: z.boolean().optional(), + licenseKey: z.string().optional(), + }), + ) + .mutation(async ({ ctx, input }) => { + const currentUserId = ctx.user.id; + + await db + .update(user) + .set({ + ...(input.enableEnterpriseFeatures === undefined + ? {} + : { enableEnterpriseFeatures: input.enableEnterpriseFeatures }), + ...(input.licenseKey === undefined + ? {} + : { licenseKey: input.licenseKey }), + }) + .where(eq(user.id, currentUserId)); + + return true; + }), +});