mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
refactor: update reset password and authentication flows
This commit removes several authentication-related components and simplifies the password reset process: - Removed login-2fa component - Deleted confirm-email page - Updated reset password logic to use Drizzle ORM directly - Removed unused authentication-related functions - Simplified server-side authentication routes
This commit is contained in:
parent
8ab6d6b282
commit
b00c12965a
@ -51,20 +51,9 @@ const baseAdmin: User = {
|
||||
serversQuantity: 0,
|
||||
stripeCustomerId: "",
|
||||
stripeSubscriptionId: "",
|
||||
accessedProjects: [],
|
||||
accessedServices: [],
|
||||
banExpires: new Date(),
|
||||
banned: true,
|
||||
banReason: "",
|
||||
canAccessToAPI: false,
|
||||
canCreateProjects: false,
|
||||
canDeleteProjects: false,
|
||||
canDeleteServices: false,
|
||||
canAccessToDocker: false,
|
||||
canAccessToSSHKeys: false,
|
||||
canCreateServices: false,
|
||||
canAccessToTraefikFiles: false,
|
||||
canAccessToGitProviders: false,
|
||||
email: "",
|
||||
expirationDate: "",
|
||||
id: "",
|
||||
@ -73,7 +62,6 @@ const baseAdmin: User = {
|
||||
createdAt2: new Date().toISOString(),
|
||||
emailVerified: false,
|
||||
image: "",
|
||||
token: "",
|
||||
updatedAt: new Date(),
|
||||
twoFactorEnabled: false,
|
||||
};
|
||||
|
@ -1,131 +0,0 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
|
||||
import { CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSlot,
|
||||
} from "@/components/ui/input-otp";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { REGEXP_ONLY_DIGITS } from "input-otp";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const Login2FASchema = z.object({
|
||||
pin: z.string().min(6, {
|
||||
message: "Pin is required",
|
||||
}),
|
||||
});
|
||||
|
||||
type Login2FA = z.infer<typeof Login2FASchema>;
|
||||
|
||||
interface Props {
|
||||
authId: string;
|
||||
}
|
||||
|
||||
export const Login2FA = ({ authId }: Props) => {
|
||||
const { push } = useRouter();
|
||||
|
||||
const { mutateAsync, isLoading, isError, error } =
|
||||
api.auth.verifyLogin2FA.useMutation();
|
||||
|
||||
const form = useForm<Login2FA>({
|
||||
defaultValues: {
|
||||
pin: "",
|
||||
},
|
||||
resolver: zodResolver(Login2FASchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
pin: "",
|
||||
});
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
||||
|
||||
const onSubmit = async (data: Login2FA) => {
|
||||
await mutateAsync({
|
||||
pin: data.pin,
|
||||
id: authId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Signin successfully", {
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
push("/dashboard/projects");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Signin failed", {
|
||||
duration: 2000,
|
||||
});
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
{isError && (
|
||||
<div className="flex flex-row gap-4 rounded-lg items-center bg-red-50 p-2 dark:bg-red-950">
|
||||
<AlertTriangle className="text-red-600 dark:text-red-400" />
|
||||
<span className="text-sm text-red-600 dark:text-red-400">
|
||||
{error?.message}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<CardTitle className="text-xl font-bold">2FA Login</CardTitle>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="pin"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col max-sm:items-center">
|
||||
<FormLabel>Pin</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex">
|
||||
<InputOTP
|
||||
maxLength={6}
|
||||
{...field}
|
||||
pattern={REGEXP_ONLY_DIGITS}
|
||||
>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} className="border-border" />
|
||||
<InputOTPSlot index={1} className="border-border" />
|
||||
<InputOTPSlot index={2} className="border-border" />
|
||||
<InputOTPSlot index={3} className="border-border" />
|
||||
<InputOTPSlot index={4} className="border-border" />
|
||||
<InputOTPSlot index={5} className="border-border" />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Please enter the 6 digits code provided by your authenticator
|
||||
app.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button isLoading={isLoading} type="submit">
|
||||
Submit 2FA
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
@ -72,7 +72,7 @@ export const ShowPaidMonitoring = ({
|
||||
data,
|
||||
isLoading,
|
||||
error: queryError,
|
||||
} = api.user.getServerMetrics.useQuery(
|
||||
} = api.server.getServerMetrics.useQuery(
|
||||
{
|
||||
url: BASE_URL,
|
||||
token,
|
||||
|
@ -44,6 +44,12 @@ const PinSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
type TwoFactorSetupData = {
|
||||
qrCodeUrl: string;
|
||||
secret: string;
|
||||
totpURI: string;
|
||||
};
|
||||
|
||||
type PasswordForm = z.infer<typeof PasswordSchema>;
|
||||
type PinForm = z.infer<typeof PinSchema>;
|
||||
|
||||
|
@ -80,7 +80,7 @@ export const UpdateServerIp = ({ children }: Props) => {
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Server IP Updated");
|
||||
await utils.admin.one.invalidate();
|
||||
await utils.user.get.invalidate();
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { buffer } from "node:stream/consumers";
|
||||
import { db } from "@/server/db";
|
||||
import { organization, server, users_temp } from "@/server/db/schema";
|
||||
import { findUserById, type Server } from "@dokploy/server";
|
||||
import { type Server, findUserById } from "@dokploy/server";
|
||||
import { asc, eq } from "drizzle-orm";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import Stripe from "stripe";
|
||||
|
@ -1,96 +0,0 @@
|
||||
import { OnboardingLayout } from "@/components/layouts/onboarding-layout";
|
||||
import { Logo } from "@/components/shared/logo";
|
||||
import { CardDescription, CardTitle } from "@/components/ui/card";
|
||||
import { db } from "@/server/db";
|
||||
import { auth } from "@/server/db/schema";
|
||||
import { IS_CLOUD, updateAuthById } from "@dokploy/server";
|
||||
import { isBefore } from "date-fns";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import type { ReactElement } from "react";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center ">
|
||||
<div className="flex flex-col items-center gap-4 w-full">
|
||||
<Link href="/" className="flex flex-row items-center gap-2">
|
||||
<Logo />
|
||||
<span className="font-medium text-sm">Dokploy</span>
|
||||
</Link>
|
||||
<CardTitle className="text-2xl font-bold">Email Confirmed</CardTitle>
|
||||
<CardDescription>
|
||||
Congratulations, your email is confirmed.
|
||||
</CardDescription>
|
||||
<div>
|
||||
<Link href="/" className="w-full text-primary">
|
||||
Click here to login
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Home.getLayout = (page: ReactElement) => {
|
||||
return <OnboardingLayout>{page}</OnboardingLayout>;
|
||||
};
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
if (!IS_CLOUD) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/",
|
||||
},
|
||||
};
|
||||
}
|
||||
const { token } = context.query;
|
||||
|
||||
if (typeof token !== "string") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const authR = await db.query.auth.findFirst({
|
||||
where: eq(auth.confirmationToken, token),
|
||||
});
|
||||
|
||||
if (
|
||||
!authR ||
|
||||
authR?.confirmationToken === null ||
|
||||
authR?.confirmationExpiresAt === null
|
||||
) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const isExpired = isBefore(new Date(authR.confirmationExpiresAt), new Date());
|
||||
|
||||
if (isExpired) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
await updateAuthById(authR.id, {
|
||||
confirmationToken: null,
|
||||
confirmationExpiresAt: null,
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
token: authR.confirmationToken,
|
||||
},
|
||||
};
|
||||
}
|
@ -85,6 +85,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (data?.twoFactorRedirect as boolean) {
|
||||
setTwoFactorCode("");
|
||||
setIsTwoFactor(true);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Login2FA } from "@/components/auth/login-2fa";
|
||||
import { OnboardingLayout } from "@/components/layouts/onboarding-layout";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Logo } from "@/components/shared/logo";
|
||||
@ -126,9 +125,7 @@ export default function Home() {
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
) : (
|
||||
<Login2FA authId={temp.authId} />
|
||||
)}
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-row justify-between flex-wrap">
|
||||
<div className="mt-4 text-center text-sm flex flex-row justify-center gap-2">
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { findAdmin } from "@dokploy/server";
|
||||
import { updateAuthById } from "@dokploy/server";
|
||||
import { generateRandomPassword } from "@dokploy/server";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { account } from "@dokploy/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
@ -8,9 +10,12 @@ import { generateRandomPassword } from "@dokploy/server";
|
||||
|
||||
const result = await findAdmin();
|
||||
|
||||
const update = await updateAuthById(result.authId, {
|
||||
password: randomPassword.hashedPassword,
|
||||
});
|
||||
const update = await db
|
||||
.update(account)
|
||||
.set({
|
||||
password: randomPassword.hashedPassword,
|
||||
})
|
||||
.where(eq(account.userId, result.userId));
|
||||
|
||||
if (update) {
|
||||
console.log("Password reset successful");
|
||||
|
@ -14,13 +14,11 @@ import {
|
||||
IS_CLOUD,
|
||||
findUserById,
|
||||
getUserByToken,
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
validateRequest,
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import * as bcrypt from "bcrypt";
|
||||
import { isBefore } from "date-fns";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
@ -321,157 +319,64 @@ export const authRouter = createTRPCRouter({
|
||||
`,
|
||||
);
|
||||
}),
|
||||
|
||||
resetPassword: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
resetPasswordToken: z.string().min(1),
|
||||
password: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
if (!IS_CLOUD) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "This feature is only available in the cloud version",
|
||||
});
|
||||
}
|
||||
const authR = await db.query.auth.findFirst({
|
||||
where: eq(auth.resetPasswordToken, input.resetPasswordToken),
|
||||
});
|
||||
|
||||
if (!authR || authR.resetPasswordExpiresAt === null) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Token not found",
|
||||
});
|
||||
}
|
||||
|
||||
const isExpired = isBefore(
|
||||
new Date(authR.resetPasswordExpiresAt),
|
||||
new Date(),
|
||||
);
|
||||
|
||||
if (isExpired) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Token expired",
|
||||
});
|
||||
}
|
||||
|
||||
await updateAuthById(authR.id, {
|
||||
resetPasswordExpiresAt: null,
|
||||
resetPasswordToken: null,
|
||||
password: bcrypt.hashSync(input.password, 10),
|
||||
});
|
||||
|
||||
return true;
|
||||
}),
|
||||
confirmEmail: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
confirmationToken: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
if (!IS_CLOUD) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Functionality not available in cloud version",
|
||||
});
|
||||
}
|
||||
const authR = await db.query.auth.findFirst({
|
||||
where: eq(auth.confirmationToken, input.confirmationToken),
|
||||
});
|
||||
if (!authR || authR.confirmationExpiresAt === null) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Token not found",
|
||||
});
|
||||
}
|
||||
if (authR.confirmationToken !== input.confirmationToken) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Confirmation Token not found",
|
||||
});
|
||||
}
|
||||
|
||||
const isExpired = isBefore(
|
||||
new Date(authR.confirmationExpiresAt),
|
||||
new Date(),
|
||||
);
|
||||
|
||||
if (isExpired) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Confirmation Token expired",
|
||||
});
|
||||
}
|
||||
1;
|
||||
await updateAuthById(authR.id, {
|
||||
confirmationToken: null,
|
||||
confirmationExpiresAt: null,
|
||||
});
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
|
||||
export const sendVerificationEmail = async (authId: string) => {
|
||||
const token = nanoid();
|
||||
const result = await updateAuthById(authId, {
|
||||
confirmationToken: token,
|
||||
confirmationExpiresAt: new Date(
|
||||
new Date().getTime() + 24 * 60 * 60 * 1000,
|
||||
).toISOString(),
|
||||
});
|
||||
// export const sendVerificationEmail = async (authId: string) => {
|
||||
// const token = nanoid();
|
||||
// const result = await updateAuthById(authId, {
|
||||
// confirmationToken: token,
|
||||
// confirmationExpiresAt: new Date(
|
||||
// new Date().getTime() + 24 * 60 * 60 * 1000,
|
||||
// ).toISOString(),
|
||||
// });
|
||||
|
||||
if (!result) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "User not found",
|
||||
});
|
||||
}
|
||||
await sendEmailNotification(
|
||||
{
|
||||
fromAddress: process.env.SMTP_FROM_ADDRESS || "",
|
||||
toAddresses: [result?.email],
|
||||
smtpServer: process.env.SMTP_SERVER || "",
|
||||
smtpPort: Number(process.env.SMTP_PORT),
|
||||
username: process.env.SMTP_USERNAME || "",
|
||||
password: process.env.SMTP_PASSWORD || "",
|
||||
},
|
||||
"Confirm your email | Dokploy",
|
||||
`
|
||||
Welcome to Dokploy!
|
||||
Please confirm your email by clicking the link below:
|
||||
<a href="${WEBSITE_URL}/confirm-email?token=${result?.confirmationToken}">
|
||||
Confirm Email
|
||||
</a>
|
||||
`,
|
||||
);
|
||||
// if (!result) {
|
||||
// throw new TRPCError({
|
||||
// code: "BAD_REQUEST",
|
||||
// message: "User not found",
|
||||
// });
|
||||
// }
|
||||
// await sendEmailNotification(
|
||||
// {
|
||||
// fromAddress: process.env.SMTP_FROM_ADDRESS || "",
|
||||
// toAddresses: [result?.email],
|
||||
// smtpServer: process.env.SMTP_SERVER || "",
|
||||
// smtpPort: Number(process.env.SMTP_PORT),
|
||||
// username: process.env.SMTP_USERNAME || "",
|
||||
// password: process.env.SMTP_PASSWORD || "",
|
||||
// },
|
||||
// "Confirm your email | Dokploy",
|
||||
// `
|
||||
// Welcome to Dokploy!
|
||||
// Please confirm your email by clicking the link below:
|
||||
// <a href="${WEBSITE_URL}/confirm-email?token=${result?.confirmationToken}">
|
||||
// Confirm Email
|
||||
// </a>
|
||||
// `,
|
||||
// );
|
||||
|
||||
return true;
|
||||
};
|
||||
// return true;
|
||||
// };
|
||||
|
||||
export const sendDiscordNotificationWelcome = async (newAdmin: Auth) => {
|
||||
await sendDiscordNotification(
|
||||
{
|
||||
webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
|
||||
},
|
||||
{
|
||||
title: "New User Registered",
|
||||
color: 0x00ff00,
|
||||
fields: [
|
||||
{
|
||||
name: "Email",
|
||||
value: newAdmin.email,
|
||||
inline: true,
|
||||
},
|
||||
],
|
||||
timestamp: newAdmin.createdAt,
|
||||
footer: {
|
||||
text: "Dokploy User Registration Notification",
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
// export const sendDiscordNotificationWelcome = async (newAdmin: Auth) => {
|
||||
// await sendDiscordNotification(
|
||||
// {
|
||||
// webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
|
||||
// },
|
||||
// {
|
||||
// title: "New User Registered",
|
||||
// color: 0x00ff00,
|
||||
// fields: [
|
||||
// {
|
||||
// name: "Email",
|
||||
// value: newAdmin.email,
|
||||
// inline: true,
|
||||
// },
|
||||
// ],
|
||||
// timestamp: newAdmin.createdAt,
|
||||
// footer: {
|
||||
// text: "Dokploy User Registration Notification",
|
||||
// },
|
||||
// },
|
||||
// );
|
||||
// };
|
||||
|
@ -37,6 +37,7 @@ import {
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { and, desc, eq, getTableColumns, isNotNull, sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
|
||||
export const serverRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
@ -378,4 +379,62 @@ export const serverRouter = createTRPCRouter({
|
||||
const ip = await getPublicIpWithFallback();
|
||||
return ip;
|
||||
}),
|
||||
getServerMetrics: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
url: z.string(),
|
||||
token: z.string(),
|
||||
dataPoints: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ 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;
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
@ -143,4 +143,63 @@ export const userRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
getContainerMetrics: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
url: z.string(),
|
||||
token: z.string(),
|
||||
appName: z.string(),
|
||||
dataPoints: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ 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;
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
@ -108,6 +108,23 @@ export const isAdminPresent = async () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const findAdmin = async () => {
|
||||
const admin = await db.query.member.findFirst({
|
||||
where: eq(member.role, "owner"),
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!admin) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Admin not found",
|
||||
});
|
||||
}
|
||||
return admin;
|
||||
};
|
||||
|
||||
export const getUserByToken = async (token: string) => {
|
||||
const user = await db.query.invitation.findFirst({
|
||||
where: eq(invitation.id, token),
|
||||
@ -154,8 +171,8 @@ export const getDokployUrl = async () => {
|
||||
}
|
||||
const admin = await findAdmin();
|
||||
|
||||
if (admin.host) {
|
||||
return `https://${admin.host}`;
|
||||
if (admin.user.host) {
|
||||
return `https://${admin.user.host}`;
|
||||
}
|
||||
return `http://${admin.serverIp}:${process.env.PORT}`;
|
||||
return `http://${admin.user.serverIp}:${process.env.PORT}`;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user