import { db } from "@/server/db"; import { apiAssignPermissions, apiCreateUserInvitation, apiFindOneToken, apiRemoveUser, apiUpdateWebServerMonitoring, } from "@/server/db/schema"; import { IS_CLOUD, createInvitation, findUserByAuthId, findUserById, getUserByToken, removeUserById, setupWebMonitoring, updateAdminById, updateUser, } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { z } from "zod"; import { adminProcedure, createTRPCRouter, protectedProcedure, publicProcedure, } from "../trpc"; export const adminRouter = createTRPCRouter({ one: adminProcedure.query(async ({ ctx }) => { const { sshPrivateKey, ...rest } = await findUserById(ctx.user.id); return { haveSSH: !!sshPrivateKey, ...rest, }; }), update: adminProcedure .input( z.object({ enableDockerCleanup: z.boolean(), }), ) .mutation(async ({ input, ctx }) => { if (ctx.user.rol === "member") { throw new TRPCError({ code: "UNAUTHORIZED", message: "You are not allowed to update this admin", }); } const user = await findUserById(ctx.user.ownerId); return updateUser(user.id, {}); }), createUserInvitation: adminProcedure .input(apiCreateUserInvitation) .mutation(async ({ input, ctx }) => { try { await createInvitation(input, ctx.user.id); } catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: "Error creating this user\ncheck if the email is not registered", cause: error, }); } }), removeUser: adminProcedure .input(apiRemoveUser) .mutation(async ({ input, ctx }) => { try { const user = await findUserById(input.id); if (user.id !== ctx.user.ownerId) { throw new TRPCError({ code: "UNAUTHORIZED", message: "You are not allowed to delete this user", }); } return await removeUserById(input.id); } catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: "Error deleting this user", cause: error, }); } }), getUserByToken: publicProcedure .input(apiFindOneToken) .query(async ({ input }) => { return await getUserByToken(input.token); }), assignPermissions: adminProcedure .input(apiAssignPermissions) .mutation(async ({ input, ctx }) => { try { const user = await findUserById(input.id); if (user.id !== ctx.user.ownerId) { throw new TRPCError({ code: "UNAUTHORIZED", message: "You are not allowed to assign permissions", }); } await updateUser(user.id, { ...input, }); // await db // .update(users) // .set({ // ...input, // }) // .where(eq(users.userId, input.userId)); } catch (error) { throw error; } }), setupMonitoring: adminProcedure .input(apiUpdateWebServerMonitoring) .mutation(async ({ input, ctx }) => { try { if (IS_CLOUD) { throw new TRPCError({ code: "UNAUTHORIZED", message: "Feature disabled on cloud", }); } const user = await findUserById(ctx.user.ownerId); if (user.id !== ctx.user.ownerId) { throw new TRPCError({ code: "UNAUTHORIZED", message: "You are not authorized to setup this server", }); } await updateUser(user.id, { metricsConfig: { server: { type: "Dokploy", refreshRate: input.metricsConfig.server.refreshRate, port: input.metricsConfig.server.port, token: input.metricsConfig.server.token, cronJob: input.metricsConfig.server.cronJob, urlCallback: input.metricsConfig.server.urlCallback, retentionDays: input.metricsConfig.server.retentionDays, thresholds: { cpu: input.metricsConfig.server.thresholds.cpu, memory: input.metricsConfig.server.thresholds.memory, }, }, containers: { refreshRate: input.metricsConfig.containers.refreshRate, services: { include: input.metricsConfig.containers.services.include || [], exclude: input.metricsConfig.containers.services.exclude || [], }, }, }, }); const currentServer = await setupWebMonitoring(user.id); return currentServer; } catch (error) { throw error; } }), getMetricsToken: protectedProcedure.query(async ({ ctx }) => { const user = await findUserById(ctx.user.ownerId); return { serverIp: user.serverIp, enabledFeatures: user.enablePaidFeatures, metricsConfig: user?.metricsConfig, }; }), getServerMetrics: protectedProcedure .input( z.object({ url: z.string(), token: z.string(), dataPoints: z.string(), }), ) .query(async ({ ctx, input }) => { try { const url = new URL(input.url); url.searchParams.append("limit", input.dataPoints); const response = await fetch(url.toString(), { headers: { Authorization: `Bearer ${input.token}`, }, }); if (!response.ok) { throw new Error( `Error ${response.status}: ${response.statusText}. Ensure the container is running and this service is included in the monitoring configuration.`, ); } const data = await response.json(); if (!Array.isArray(data) || data.length === 0) { throw new Error( [ "No monitoring data available. This could be because:", "", "1. You don't have setup the monitoring service, you can do in web server section.", "2. If you already have setup the monitoring service, wait a few minutes and refresh the page.", ].join("\n"), ); } return data as { cpu: string; cpuModel: string; cpuCores: number; cpuPhysicalCores: number; cpuSpeed: number; os: string; distro: string; kernel: string; arch: string; memUsed: string; memUsedGB: string; memTotal: string; uptime: number; diskUsed: string; totalDisk: string; networkIn: string; networkOut: string; timestamp: string; }[]; } catch (error) { throw error; } }), getContainerMetrics: protectedProcedure .input( z.object({ url: z.string(), token: z.string(), appName: z.string(), dataPoints: z.string(), }), ) .query(async ({ ctx, input }) => { try { if (!input.appName) { throw new Error( [ "No Application Selected:", "", "Make Sure to select an application to monitor.", ].join("\n"), ); } const url = new URL(`${input.url}/metrics/containers`); url.searchParams.append("limit", input.dataPoints); url.searchParams.append("appName", input.appName); const response = await fetch(url.toString(), { headers: { Authorization: `Bearer ${input.token}`, }, }); if (!response.ok) { throw new Error( `Error ${response.status}: ${response.statusText}. Please verify that the application "${input.appName}" is running and this service is included in the monitoring configuration.`, ); } const data = await response.json(); if (!Array.isArray(data) || data.length === 0) { throw new Error( [ `No monitoring data available for "${input.appName}". This could be because:`, "", "1. The container was recently started - wait a few minutes for data to be collected", "2. The container is not running - verify its status", "3. The service is not included in your monitoring configuration", ].join("\n"), ); } return data as { containerId: string; containerName: string; containerImage: string; containerLabels: string; containerCommand: string; containerCreated: string; }[]; } catch (error) { throw error; } }), });