mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Reapply "Merge branch 'canary' into kucherenko/canary"
This reverts commit e6cb6454db.
This commit is contained in:
@@ -90,7 +90,7 @@ export default function Custom404({ statusCode, error }: Props) {
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
Error.getInitialProps = ({ res, err, ...rest }: NextPageContext) => {
|
||||
Error.getInitialProps = ({ res, err }: NextPageContext) => {
|
||||
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
|
||||
return { statusCode, error: err };
|
||||
};
|
||||
|
||||
30
apps/dokploy/pages/accept-invitation/[accept-invitation].tsx
Normal file
30
apps/dokploy/pages/accept-invitation/[accept-invitation].tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export const AcceptInvitation = () => {
|
||||
const { query } = useRouter();
|
||||
|
||||
const invitationId = query["accept-invitation"];
|
||||
|
||||
// const { data: organization } = api.organization.getById.useQuery({
|
||||
// id: id as string
|
||||
// })
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const result = await authClient.organization.acceptInvitation({
|
||||
invitationId: invitationId as string,
|
||||
});
|
||||
console.log(result);
|
||||
}}
|
||||
>
|
||||
Accept Invitation
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AcceptInvitation;
|
||||
@@ -1,17 +1,11 @@
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { createTRPCContext } from "@/server/api/trpc";
|
||||
import { validateBearerToken, validateRequest } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createOpenApiNextHandler } from "@dokploy/trpc-openapi";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
let { session, user } = await validateBearerToken(req);
|
||||
|
||||
if (!session) {
|
||||
const cookieResult = await validateRequest(req, res);
|
||||
session = cookieResult.session;
|
||||
user = cookieResult.user;
|
||||
}
|
||||
const { session, user } = await validateRequest(req);
|
||||
|
||||
if (!user || !session) {
|
||||
res.status(401).json({ message: "Unauthorized" });
|
||||
|
||||
7
apps/dokploy/pages/api/auth/[...all].ts
Normal file
7
apps/dokploy/pages/api/auth/[...all].ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { auth } from "@dokploy/server/index";
|
||||
import { toNodeHandler } from "better-auth/node";
|
||||
|
||||
// Disallow body parsing, we will parse it manually
|
||||
export const config = { api: { bodyParser: false } };
|
||||
|
||||
export default toNodeHandler(auth.handler);
|
||||
@@ -3,9 +3,7 @@ import { applications, compose, github } from "@/server/db/schema";
|
||||
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||
import { myQueue } from "@/server/queues/queueSetup";
|
||||
import { deploy } from "@/server/utils/deploy";
|
||||
import { generateRandomDomain } from "@/templates/utils";
|
||||
import {
|
||||
type Domain,
|
||||
IS_CLOUD,
|
||||
createPreviewDeployment,
|
||||
findPreviewDeploymentByApplicationId,
|
||||
@@ -121,7 +119,7 @@ export default async function handler(
|
||||
if (IS_CLOUD && app.serverId) {
|
||||
jobData.serverId = app.serverId;
|
||||
await deploy(jobData);
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
@@ -156,7 +154,7 @@ export default async function handler(
|
||||
if (IS_CLOUD && composeApp.serverId) {
|
||||
jobData.serverId = composeApp.serverId;
|
||||
await deploy(jobData);
|
||||
return true;
|
||||
continue;
|
||||
}
|
||||
|
||||
await myQueue.add(
|
||||
@@ -182,8 +180,9 @@ export default async function handler(
|
||||
}
|
||||
} else if (req.headers["x-github-event"] === "pull_request") {
|
||||
const prId = githubBody?.pull_request?.id;
|
||||
const action = githubBody?.action;
|
||||
|
||||
if (githubBody?.action === "closed") {
|
||||
if (action === "closed") {
|
||||
const previewDeploymentResult =
|
||||
await findPreviewDeploymentsByPullRequestId(prId);
|
||||
|
||||
@@ -201,79 +200,86 @@ export default async function handler(
|
||||
res.status(200).json({ message: "Preview Deployment Closed" });
|
||||
return;
|
||||
}
|
||||
|
||||
// opened or synchronize or reopened
|
||||
const repository = githubBody?.repository?.name;
|
||||
const deploymentHash = githubBody?.pull_request?.head?.sha;
|
||||
const branch = githubBody?.pull_request?.base?.ref;
|
||||
const owner = githubBody?.repository?.owner?.login;
|
||||
if (
|
||||
action === "opened" ||
|
||||
action === "synchronize" ||
|
||||
action === "reopened"
|
||||
) {
|
||||
const repository = githubBody?.repository?.name;
|
||||
const deploymentHash = githubBody?.pull_request?.head?.sha;
|
||||
const branch = githubBody?.pull_request?.base?.ref;
|
||||
const owner = githubBody?.repository?.owner?.login;
|
||||
|
||||
const apps = await db.query.applications.findMany({
|
||||
where: and(
|
||||
eq(applications.sourceType, "github"),
|
||||
eq(applications.repository, repository),
|
||||
eq(applications.branch, branch),
|
||||
eq(applications.isPreviewDeploymentsActive, true),
|
||||
eq(applications.owner, owner),
|
||||
),
|
||||
with: {
|
||||
previewDeployments: true,
|
||||
},
|
||||
});
|
||||
|
||||
const prBranch = githubBody?.pull_request?.head?.ref;
|
||||
|
||||
const prNumber = githubBody?.pull_request?.number;
|
||||
const prTitle = githubBody?.pull_request?.title;
|
||||
const prURL = githubBody?.pull_request?.html_url;
|
||||
|
||||
for (const app of apps) {
|
||||
const previewLimit = app?.previewLimit || 0;
|
||||
if (app?.previewDeployments?.length > previewLimit) {
|
||||
continue;
|
||||
}
|
||||
const previewDeploymentResult =
|
||||
await findPreviewDeploymentByApplicationId(app.applicationId, prId);
|
||||
|
||||
let previewDeploymentId =
|
||||
previewDeploymentResult?.previewDeploymentId || "";
|
||||
|
||||
if (!previewDeploymentResult) {
|
||||
const previewDeployment = await createPreviewDeployment({
|
||||
applicationId: app.applicationId as string,
|
||||
branch: prBranch,
|
||||
pullRequestId: prId,
|
||||
pullRequestNumber: prNumber,
|
||||
pullRequestTitle: prTitle,
|
||||
pullRequestURL: prURL,
|
||||
});
|
||||
previewDeploymentId = previewDeployment.previewDeploymentId;
|
||||
}
|
||||
|
||||
const jobData: DeploymentJob = {
|
||||
applicationId: app.applicationId as string,
|
||||
titleLog: "Preview Deployment",
|
||||
descriptionLog: `Hash: ${deploymentHash}`,
|
||||
type: "deploy",
|
||||
applicationType: "application-preview",
|
||||
server: !!app.serverId,
|
||||
previewDeploymentId,
|
||||
};
|
||||
|
||||
if (IS_CLOUD && app.serverId) {
|
||||
jobData.serverId = app.serverId;
|
||||
await deploy(jobData);
|
||||
return true;
|
||||
}
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
{ ...jobData },
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
const apps = await db.query.applications.findMany({
|
||||
where: and(
|
||||
eq(applications.sourceType, "github"),
|
||||
eq(applications.repository, repository),
|
||||
eq(applications.branch, branch),
|
||||
eq(applications.isPreviewDeploymentsActive, true),
|
||||
eq(applications.owner, owner),
|
||||
),
|
||||
with: {
|
||||
previewDeployments: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const prBranch = githubBody?.pull_request?.head?.ref;
|
||||
|
||||
const prNumber = githubBody?.pull_request?.number;
|
||||
const prTitle = githubBody?.pull_request?.title;
|
||||
const prURL = githubBody?.pull_request?.html_url;
|
||||
|
||||
for (const app of apps) {
|
||||
const previewLimit = app?.previewLimit || 0;
|
||||
if (app?.previewDeployments?.length > previewLimit) {
|
||||
continue;
|
||||
}
|
||||
const previewDeploymentResult =
|
||||
await findPreviewDeploymentByApplicationId(app.applicationId, prId);
|
||||
|
||||
let previewDeploymentId =
|
||||
previewDeploymentResult?.previewDeploymentId || "";
|
||||
|
||||
if (!previewDeploymentResult) {
|
||||
const previewDeployment = await createPreviewDeployment({
|
||||
applicationId: app.applicationId as string,
|
||||
branch: prBranch,
|
||||
pullRequestId: prId,
|
||||
pullRequestNumber: prNumber,
|
||||
pullRequestTitle: prTitle,
|
||||
pullRequestURL: prURL,
|
||||
});
|
||||
previewDeploymentId = previewDeployment.previewDeploymentId;
|
||||
}
|
||||
|
||||
const jobData: DeploymentJob = {
|
||||
applicationId: app.applicationId as string,
|
||||
titleLog: "Preview Deployment",
|
||||
descriptionLog: `Hash: ${deploymentHash}`,
|
||||
type: "deploy",
|
||||
applicationType: "application-preview",
|
||||
server: !!app.serverId,
|
||||
previewDeploymentId,
|
||||
};
|
||||
|
||||
if (IS_CLOUD && app.serverId) {
|
||||
jobData.serverId = app.serverId;
|
||||
await deploy(jobData);
|
||||
continue;
|
||||
}
|
||||
await myQueue.add(
|
||||
"deployments",
|
||||
{ ...jobData },
|
||||
{
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
return res.status(200).json({ message: "Apps Deployed" });
|
||||
}
|
||||
return res.status(200).json({ message: "Apps Deployed" });
|
||||
}
|
||||
|
||||
return res.status(400).json({ message: "No Actions matched" });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
_req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
return res.status(200).json({ ok: true });
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { db } from "@/server/db";
|
||||
import { github } from "@/server/db/schema";
|
||||
import {
|
||||
createGithub,
|
||||
findAdminByAuthId,
|
||||
findAuthById,
|
||||
findUserByAuthId,
|
||||
} from "@dokploy/server";
|
||||
import { createGithub } from "@dokploy/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { Octokit } from "octokit";
|
||||
@@ -21,14 +16,13 @@ export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const { code, state, installation_id, setup_action }: Query =
|
||||
req.query as Query;
|
||||
const { code, state, installation_id }: Query = req.query as Query;
|
||||
|
||||
if (!code) {
|
||||
return res.status(400).json({ error: "Missing code parameter" });
|
||||
}
|
||||
const [action, value] = state?.split(":");
|
||||
// Value could be the authId or the githubProviderId
|
||||
// Value could be the organizationId or the githubProviderId
|
||||
|
||||
if (action === "gh_init") {
|
||||
const octokit = new Octokit({});
|
||||
@@ -39,17 +33,6 @@ export default async function handler(
|
||||
},
|
||||
);
|
||||
|
||||
const auth = await findAuthById(value as string);
|
||||
|
||||
let adminId = "";
|
||||
if (auth.rol === "admin") {
|
||||
const admin = await findAdminByAuthId(auth.id);
|
||||
adminId = admin.adminId;
|
||||
} else {
|
||||
const user = await findUserByAuthId(auth.id);
|
||||
adminId = user.adminId;
|
||||
}
|
||||
|
||||
await createGithub(
|
||||
{
|
||||
name: data.name,
|
||||
@@ -60,7 +43,7 @@ export default async function handler(
|
||||
githubWebhookSecret: data.webhook_secret,
|
||||
githubPrivateKey: data.pem,
|
||||
},
|
||||
adminId,
|
||||
value as string,
|
||||
);
|
||||
} else if (action === "gh_setup") {
|
||||
await db
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { buffer } from "node:stream/consumers";
|
||||
import { db } from "@/server/db";
|
||||
import { admins, server } from "@/server/db/schema";
|
||||
import { findAdminById } from "@dokploy/server";
|
||||
import { organization, server, users_temp } from "@/server/db/schema";
|
||||
import { type Server, findUserById } from "@dokploy/server";
|
||||
import { asc, eq } from "drizzle-orm";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import Stripe from "stripe";
|
||||
@@ -64,33 +64,35 @@ export default async function handler(
|
||||
session.subscription as string,
|
||||
);
|
||||
await db
|
||||
.update(admins)
|
||||
.update(users_temp)
|
||||
.set({
|
||||
stripeCustomerId: session.customer as string,
|
||||
stripeSubscriptionId: session.subscription as string,
|
||||
serversQuantity: subscription?.items?.data?.[0]?.quantity ?? 0,
|
||||
})
|
||||
.where(eq(admins.adminId, adminId))
|
||||
.where(eq(users_temp.id, adminId))
|
||||
.returning();
|
||||
|
||||
const admin = await findAdminById(adminId);
|
||||
const admin = await findUserById(adminId);
|
||||
if (!admin) {
|
||||
return res.status(400).send("Webhook Error: Admin not found");
|
||||
}
|
||||
const newServersQuantity = admin.serversQuantity;
|
||||
await updateServersBasedOnQuantity(admin.adminId, newServersQuantity);
|
||||
await updateServersBasedOnQuantity(admin.id, newServersQuantity);
|
||||
break;
|
||||
}
|
||||
case "customer.subscription.created": {
|
||||
const newSubscription = event.data.object as Stripe.Subscription;
|
||||
|
||||
await db
|
||||
.update(admins)
|
||||
.update(users_temp)
|
||||
.set({
|
||||
stripeSubscriptionId: newSubscription.id,
|
||||
stripeCustomerId: newSubscription.customer as string,
|
||||
})
|
||||
.where(eq(admins.stripeCustomerId, newSubscription.customer as string))
|
||||
.where(
|
||||
eq(users_temp.stripeCustomerId, newSubscription.customer as string),
|
||||
)
|
||||
.returning();
|
||||
|
||||
break;
|
||||
@@ -100,14 +102,16 @@ export default async function handler(
|
||||
const newSubscription = event.data.object as Stripe.Subscription;
|
||||
|
||||
await db
|
||||
.update(admins)
|
||||
.update(users_temp)
|
||||
.set({
|
||||
stripeSubscriptionId: null,
|
||||
serversQuantity: 0,
|
||||
})
|
||||
.where(eq(admins.stripeCustomerId, newSubscription.customer as string));
|
||||
.where(
|
||||
eq(users_temp.stripeCustomerId, newSubscription.customer as string),
|
||||
);
|
||||
|
||||
const admin = await findAdminByStripeCustomerId(
|
||||
const admin = await findUserByStripeCustomerId(
|
||||
newSubscription.customer as string,
|
||||
);
|
||||
|
||||
@@ -115,13 +119,13 @@ export default async function handler(
|
||||
return res.status(400).send("Webhook Error: Admin not found");
|
||||
}
|
||||
|
||||
await disableServers(admin.adminId);
|
||||
await disableServers(admin.id);
|
||||
break;
|
||||
}
|
||||
case "customer.subscription.updated": {
|
||||
const newSubscription = event.data.object as Stripe.Subscription;
|
||||
|
||||
const admin = await findAdminByStripeCustomerId(
|
||||
const admin = await findUserByStripeCustomerId(
|
||||
newSubscription.customer as string,
|
||||
);
|
||||
|
||||
@@ -131,23 +135,23 @@ export default async function handler(
|
||||
|
||||
if (newSubscription.status === "active") {
|
||||
await db
|
||||
.update(admins)
|
||||
.update(users_temp)
|
||||
.set({
|
||||
serversQuantity: newSubscription?.items?.data?.[0]?.quantity ?? 0,
|
||||
})
|
||||
.where(
|
||||
eq(admins.stripeCustomerId, newSubscription.customer as string),
|
||||
eq(users_temp.stripeCustomerId, newSubscription.customer as string),
|
||||
);
|
||||
|
||||
const newServersQuantity = admin.serversQuantity;
|
||||
await updateServersBasedOnQuantity(admin.adminId, newServersQuantity);
|
||||
await updateServersBasedOnQuantity(admin.id, newServersQuantity);
|
||||
} else {
|
||||
await disableServers(admin.adminId);
|
||||
await disableServers(admin.id);
|
||||
await db
|
||||
.update(admins)
|
||||
.update(users_temp)
|
||||
.set({ serversQuantity: 0 })
|
||||
.where(
|
||||
eq(admins.stripeCustomerId, newSubscription.customer as string),
|
||||
eq(users_temp.stripeCustomerId, newSubscription.customer as string),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -168,13 +172,13 @@ export default async function handler(
|
||||
}
|
||||
|
||||
await db
|
||||
.update(admins)
|
||||
.update(users_temp)
|
||||
.set({
|
||||
serversQuantity: suscription?.items?.data?.[0]?.quantity ?? 0,
|
||||
})
|
||||
.where(eq(admins.stripeCustomerId, suscription.customer as string));
|
||||
.where(eq(users_temp.stripeCustomerId, suscription.customer as string));
|
||||
|
||||
const admin = await findAdminByStripeCustomerId(
|
||||
const admin = await findUserByStripeCustomerId(
|
||||
suscription.customer as string,
|
||||
);
|
||||
|
||||
@@ -182,7 +186,7 @@ export default async function handler(
|
||||
return res.status(400).send("Webhook Error: Admin not found");
|
||||
}
|
||||
const newServersQuantity = admin.serversQuantity;
|
||||
await updateServersBasedOnQuantity(admin.adminId, newServersQuantity);
|
||||
await updateServersBasedOnQuantity(admin.id, newServersQuantity);
|
||||
break;
|
||||
}
|
||||
case "invoice.payment_failed": {
|
||||
@@ -193,7 +197,7 @@ export default async function handler(
|
||||
);
|
||||
|
||||
if (subscription.status !== "active") {
|
||||
const admin = await findAdminByStripeCustomerId(
|
||||
const admin = await findUserByStripeCustomerId(
|
||||
newInvoice.customer as string,
|
||||
);
|
||||
|
||||
@@ -201,13 +205,15 @@ export default async function handler(
|
||||
return res.status(400).send("Webhook Error: Admin not found");
|
||||
}
|
||||
await db
|
||||
.update(admins)
|
||||
.update(users_temp)
|
||||
.set({
|
||||
serversQuantity: 0,
|
||||
})
|
||||
.where(eq(admins.stripeCustomerId, newInvoice.customer as string));
|
||||
.where(
|
||||
eq(users_temp.stripeCustomerId, newInvoice.customer as string),
|
||||
);
|
||||
|
||||
await disableServers(admin.adminId);
|
||||
await disableServers(admin.id);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -216,20 +222,20 @@ export default async function handler(
|
||||
case "customer.deleted": {
|
||||
const customer = event.data.object as Stripe.Customer;
|
||||
|
||||
const admin = await findAdminByStripeCustomerId(customer.id);
|
||||
const admin = await findUserByStripeCustomerId(customer.id);
|
||||
if (!admin) {
|
||||
return res.status(400).send("Webhook Error: Admin not found");
|
||||
}
|
||||
|
||||
await disableServers(admin.adminId);
|
||||
await disableServers(admin.id);
|
||||
await db
|
||||
.update(admins)
|
||||
.update(users_temp)
|
||||
.set({
|
||||
stripeCustomerId: null,
|
||||
stripeSubscriptionId: null,
|
||||
serversQuantity: 0,
|
||||
})
|
||||
.where(eq(admins.stripeCustomerId, customer.id));
|
||||
.where(eq(users_temp.stripeCustomerId, customer.id));
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -240,20 +246,26 @@ export default async function handler(
|
||||
return res.status(200).json({ received: true });
|
||||
}
|
||||
|
||||
const disableServers = async (adminId: string) => {
|
||||
await db
|
||||
.update(server)
|
||||
.set({
|
||||
serverStatus: "inactive",
|
||||
})
|
||||
.where(eq(server.adminId, adminId));
|
||||
const disableServers = async (userId: string) => {
|
||||
const organizations = await db.query.organization.findMany({
|
||||
where: eq(organization.ownerId, userId),
|
||||
});
|
||||
|
||||
for (const org of organizations) {
|
||||
await db
|
||||
.update(server)
|
||||
.set({
|
||||
serverStatus: "inactive",
|
||||
})
|
||||
.where(eq(server.organizationId, org.id));
|
||||
}
|
||||
};
|
||||
|
||||
const findAdminByStripeCustomerId = async (stripeCustomerId: string) => {
|
||||
const admin = db.query.admins.findFirst({
|
||||
where: eq(admins.stripeCustomerId, stripeCustomerId),
|
||||
const findUserByStripeCustomerId = async (stripeCustomerId: string) => {
|
||||
const user = db.query.users_temp.findFirst({
|
||||
where: eq(users_temp.stripeCustomerId, stripeCustomerId),
|
||||
});
|
||||
return admin;
|
||||
return user;
|
||||
};
|
||||
|
||||
const activateServer = async (serverId: string) => {
|
||||
@@ -270,19 +282,27 @@ const deactivateServer = async (serverId: string) => {
|
||||
.where(eq(server.serverId, serverId));
|
||||
};
|
||||
|
||||
export const findServersByAdminIdSorted = async (adminId: string) => {
|
||||
const servers = await db.query.server.findMany({
|
||||
where: eq(server.adminId, adminId),
|
||||
orderBy: asc(server.createdAt),
|
||||
export const findServersByUserIdSorted = async (userId: string) => {
|
||||
const organizations = await db.query.organization.findMany({
|
||||
where: eq(organization.ownerId, userId),
|
||||
});
|
||||
|
||||
const servers: Server[] = [];
|
||||
for (const org of organizations) {
|
||||
const serversByOrg = await db.query.server.findMany({
|
||||
where: eq(server.organizationId, org.id),
|
||||
orderBy: asc(server.createdAt),
|
||||
});
|
||||
servers.push(...serversByOrg);
|
||||
}
|
||||
|
||||
return servers;
|
||||
};
|
||||
export const updateServersBasedOnQuantity = async (
|
||||
adminId: string,
|
||||
userId: string,
|
||||
newServersQuantity: number,
|
||||
) => {
|
||||
const servers = await findServersByAdminIdSorted(adminId);
|
||||
const servers = await findServersByUserIdSorted(userId);
|
||||
|
||||
if (servers.length > newServersQuantity) {
|
||||
for (const [index, server] of servers.entries()) {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ShowContainers } from "@/components/dashboard/docker/show/show-containers";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import { IS_CLOUD } from "@dokploy/server/constants";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Dashboard = () => {
|
||||
@@ -27,7 +28,7 @@ export async function getServerSideProps(
|
||||
},
|
||||
};
|
||||
}
|
||||
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||
const { user, session } = await validateRequest(ctx.req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -44,21 +45,20 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
try {
|
||||
await helpers.project.all.prefetch();
|
||||
const auth = await helpers.auth.get.fetch();
|
||||
|
||||
if (auth.rol === "user") {
|
||||
const user = await helpers.user.byAuthId.fetch({
|
||||
authId: auth.id,
|
||||
if (user.role === "member") {
|
||||
const userR = await helpers.user.one.fetch({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (!user.canAccessToDocker) {
|
||||
if (!userR?.canAccessToDocker) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -72,7 +72,7 @@ export async function getServerSideProps(
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
|
||||
@@ -1,11 +1,86 @@
|
||||
import { ShowMonitoring } from "@/components/dashboard/monitoring/web-server/show";
|
||||
import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring";
|
||||
import { ShowPaidMonitoring } from "@/components/dashboard/monitoring/paid/servers/show-paid-monitoring";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||
import { api } from "@/utils/api";
|
||||
import { IS_CLOUD } from "@dokploy/server/constants";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
|
||||
const BASE_URL = "http://localhost:3001/metrics";
|
||||
|
||||
const DEFAULT_TOKEN = "metrics";
|
||||
|
||||
const Dashboard = () => {
|
||||
return <ShowMonitoring />;
|
||||
const [toggleMonitoring, _setToggleMonitoring] = useLocalStorage(
|
||||
"monitoring-enabled",
|
||||
false,
|
||||
);
|
||||
|
||||
const { data: monitoring, isLoading } = api.user.getMetricsToken.useQuery();
|
||||
return (
|
||||
<div className="space-y-4 pb-10">
|
||||
{/* <AlertBlock>
|
||||
You are watching the <strong>Free</strong> plan.{" "}
|
||||
<a
|
||||
href="https://dokploy.com#pricing"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Upgrade
|
||||
</a>{" "}
|
||||
to get more features.
|
||||
</AlertBlock> */}
|
||||
{isLoading ? (
|
||||
<Card className="bg-sidebar p-2.5 rounded-xl mx-auto items-center">
|
||||
<div className="rounded-xl bg-background flex shadow-md px-4 min-h-[50vh] justify-center items-center text-muted-foreground">
|
||||
Loading...
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
<>
|
||||
{/* {monitoring?.enabledFeatures && (
|
||||
<div className="flex flex-row border w-fit p-4 rounded-lg items-center gap-2">
|
||||
<Label className="text-muted-foreground">Change Monitoring</Label>
|
||||
<Switch
|
||||
checked={toggleMonitoring}
|
||||
onCheckedChange={setToggleMonitoring}
|
||||
/>
|
||||
</div>
|
||||
)} */}
|
||||
{toggleMonitoring ? (
|
||||
<Card className="bg-sidebar p-2.5 rounded-xl mx-auto">
|
||||
<div className="rounded-xl bg-background shadow-md">
|
||||
<ShowPaidMonitoring
|
||||
BASE_URL={
|
||||
process.env.NODE_ENV === "production"
|
||||
? `http://${monitoring?.serverIp}:${monitoring?.metricsConfig?.server?.port}/metrics`
|
||||
: BASE_URL
|
||||
}
|
||||
token={
|
||||
process.env.NODE_ENV === "production"
|
||||
? monitoring?.metricsConfig?.server?.token
|
||||
: DEFAULT_TOKEN
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl">
|
||||
<div className="rounded-xl bg-background shadow-md p-6">
|
||||
<ContainerFreeMonitoring appName="dokploy" />
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
@@ -24,7 +99,7 @@ export async function getServerSideProps(
|
||||
},
|
||||
};
|
||||
}
|
||||
const { user } = await validateRequest(ctx.req, ctx.res);
|
||||
const { user } = await validateRequest(ctx.req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { ProjectLayout } from "@/components/layouts/project-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
@@ -49,27 +51,34 @@ import { cn } from "@/lib/utils";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import type { findProjectById } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import {
|
||||
Ban,
|
||||
Check,
|
||||
CheckCircle2,
|
||||
ChevronsUpDown,
|
||||
CircuitBoard,
|
||||
FolderInput,
|
||||
GlobeIcon,
|
||||
Loader2,
|
||||
PlusIcon,
|
||||
Search,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { Check, ChevronsUpDown, X } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
InferGetServerSidePropsType,
|
||||
} from "next";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useMemo, useState, type ReactElement } from "react";
|
||||
import { type ReactElement, useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import superjson from "superjson";
|
||||
|
||||
export type Services = {
|
||||
appName: string;
|
||||
serverId?: string | null;
|
||||
name: string;
|
||||
type:
|
||||
| "mariadb"
|
||||
@@ -90,72 +99,86 @@ type Project = Awaited<ReturnType<typeof findProjectById>>;
|
||||
export const extractServices = (data: Project | undefined) => {
|
||||
const applications: Services[] =
|
||||
data?.applications.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "application",
|
||||
id: item.applicationId,
|
||||
createdAt: item.createdAt,
|
||||
status: item.applicationStatus,
|
||||
description: item.description,
|
||||
serverId: item.serverId,
|
||||
})) || [];
|
||||
|
||||
const mariadb: Services[] =
|
||||
data?.mariadb.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "mariadb",
|
||||
id: item.mariadbId,
|
||||
createdAt: item.createdAt,
|
||||
status: item.applicationStatus,
|
||||
description: item.description,
|
||||
serverId: item.serverId,
|
||||
})) || [];
|
||||
|
||||
const postgres: Services[] =
|
||||
data?.postgres.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "postgres",
|
||||
id: item.postgresId,
|
||||
createdAt: item.createdAt,
|
||||
status: item.applicationStatus,
|
||||
description: item.description,
|
||||
serverId: item.serverId,
|
||||
})) || [];
|
||||
|
||||
const mongo: Services[] =
|
||||
data?.mongo.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "mongo",
|
||||
id: item.mongoId,
|
||||
createdAt: item.createdAt,
|
||||
status: item.applicationStatus,
|
||||
description: item.description,
|
||||
serverId: item.serverId,
|
||||
})) || [];
|
||||
|
||||
const redis: Services[] =
|
||||
data?.redis.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "redis",
|
||||
id: item.redisId,
|
||||
createdAt: item.createdAt,
|
||||
status: item.applicationStatus,
|
||||
description: item.description,
|
||||
serverId: item.serverId,
|
||||
})) || [];
|
||||
|
||||
const mysql: Services[] =
|
||||
data?.mysql.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "mysql",
|
||||
id: item.mysqlId,
|
||||
createdAt: item.createdAt,
|
||||
status: item.applicationStatus,
|
||||
description: item.description,
|
||||
serverId: item.serverId,
|
||||
})) || [];
|
||||
|
||||
const compose: Services[] =
|
||||
data?.compose.map((item) => ({
|
||||
appName: item.appName,
|
||||
name: item.name,
|
||||
type: "compose",
|
||||
id: item.composeId,
|
||||
createdAt: item.createdAt,
|
||||
status: item.composeStatus,
|
||||
description: item.description,
|
||||
serverId: item.serverId,
|
||||
})) || [];
|
||||
|
||||
applications.push(
|
||||
@@ -177,17 +200,11 @@ export const extractServices = (data: Project | undefined) => {
|
||||
const Project = (
|
||||
props: InferGetServerSidePropsType<typeof getServerSideProps>,
|
||||
) => {
|
||||
const [isBulkActionLoading, setIsBulkActionLoading] = useState(false);
|
||||
const { projectId } = props;
|
||||
const { data: auth } = api.auth.get.useQuery();
|
||||
const { data: user } = api.user.byAuthId.useQuery(
|
||||
{
|
||||
authId: auth?.id || "",
|
||||
},
|
||||
{
|
||||
enabled: !!auth?.id && auth?.rol === "user",
|
||||
},
|
||||
);
|
||||
const { data, isLoading } = api.project.one.useQuery({ projectId });
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
|
||||
const { data, isLoading, refetch } = api.project.one.useQuery({ projectId });
|
||||
const router = useRouter();
|
||||
|
||||
const emptyServices =
|
||||
@@ -214,6 +231,70 @@ const Project = (
|
||||
|
||||
const [selectedTypes, setSelectedTypes] = useState<string[]>([]);
|
||||
const [openCombobox, setOpenCombobox] = useState(false);
|
||||
const [selectedServices, setSelectedServices] = useState<string[]>([]);
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
|
||||
const handleSelectAll = () => {
|
||||
if (selectedServices.length === filteredServices.length) {
|
||||
setSelectedServices([]);
|
||||
} else {
|
||||
setSelectedServices(filteredServices.map((service) => service.id));
|
||||
}
|
||||
};
|
||||
|
||||
const handleServiceSelect = (serviceId: string, event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
setSelectedServices((prev) =>
|
||||
prev.includes(serviceId)
|
||||
? prev.filter((id) => id !== serviceId)
|
||||
: [...prev, serviceId],
|
||||
);
|
||||
};
|
||||
|
||||
const composeActions = {
|
||||
start: api.compose.start.useMutation(),
|
||||
stop: api.compose.stop.useMutation(),
|
||||
};
|
||||
|
||||
const handleBulkStart = async () => {
|
||||
let success = 0;
|
||||
setIsBulkActionLoading(true);
|
||||
for (const serviceId of selectedServices) {
|
||||
try {
|
||||
await composeActions.start.mutateAsync({ composeId: serviceId });
|
||||
success++;
|
||||
} catch (_error) {
|
||||
toast.error(`Error starting service ${serviceId}`);
|
||||
}
|
||||
}
|
||||
if (success > 0) {
|
||||
toast.success(`${success} services started successfully`);
|
||||
refetch();
|
||||
}
|
||||
setIsBulkActionLoading(false);
|
||||
setSelectedServices([]);
|
||||
setIsDropdownOpen(false);
|
||||
};
|
||||
|
||||
const handleBulkStop = async () => {
|
||||
let success = 0;
|
||||
setIsBulkActionLoading(true);
|
||||
for (const serviceId of selectedServices) {
|
||||
try {
|
||||
await composeActions.stop.mutateAsync({ composeId: serviceId });
|
||||
success++;
|
||||
} catch (_error) {
|
||||
toast.error(`Error stopping service ${serviceId}`);
|
||||
}
|
||||
}
|
||||
if (success > 0) {
|
||||
toast.success(`${success} services stopped successfully`);
|
||||
refetch();
|
||||
}
|
||||
setSelectedServices([]);
|
||||
setIsDropdownOpen(false);
|
||||
setIsBulkActionLoading(false);
|
||||
};
|
||||
|
||||
const filteredServices = useMemo(() => {
|
||||
if (!applications) return [];
|
||||
@@ -241,16 +322,16 @@ const Project = (
|
||||
<div className="w-full">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl ">
|
||||
<div className="rounded-xl bg-background shadow-md ">
|
||||
<div className="flex justify-between gap-4 w-full items-center">
|
||||
<CardHeader className="">
|
||||
<div className="flex justify-between gap-4 w-full items-center flex-wrap p-6">
|
||||
<CardHeader className="p-0">
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
<FolderInput className="size-6 text-muted-foreground self-center" />
|
||||
{data?.name}
|
||||
</CardTitle>
|
||||
<CardDescription>{data?.description}</CardDescription>
|
||||
</CardHeader>
|
||||
{(auth?.rol === "admin" || user?.canCreateServices) && (
|
||||
<div className="flex flex-row gap-4 flex-wrap px-4">
|
||||
{(auth?.role === "owner" || auth?.canCreateServices) && (
|
||||
<div className="flex flex-row gap-4 flex-wrap">
|
||||
<ProjectEnvironment projectId={projectId}>
|
||||
<Button variant="outline">Project Environment</Button>
|
||||
</ProjectEnvironment>
|
||||
@@ -299,78 +380,151 @@ const Project = (
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<div className="w-full relative">
|
||||
<Input
|
||||
placeholder="Filter services..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pr-10"
|
||||
/>
|
||||
<Search className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={selectedServices.length > 0}
|
||||
className={cn(
|
||||
"data-[state=checked]:bg-primary",
|
||||
selectedServices.length > 0 &&
|
||||
selectedServices.length <
|
||||
filteredServices.length &&
|
||||
"bg-primary/50",
|
||||
)}
|
||||
onCheckedChange={handleSelectAll}
|
||||
/>
|
||||
<span className="text-sm">
|
||||
Select All{" "}
|
||||
{selectedServices.length > 0 &&
|
||||
`(${selectedServices.length}/${filteredServices.length})`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<DropdownMenu
|
||||
open={isDropdownOpen}
|
||||
onOpenChange={setIsDropdownOpen}
|
||||
>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={selectedServices.length === 0}
|
||||
isLoading={isBulkActionLoading}
|
||||
>
|
||||
Bulk Actions
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DialogAction
|
||||
title="Start Services"
|
||||
description={`Are you sure you want to start ${selectedServices.length} services?`}
|
||||
type="default"
|
||||
onClick={handleBulkStart}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full justify-start"
|
||||
>
|
||||
<CheckCircle2 className="mr-2 h-4 w-4" />
|
||||
Start
|
||||
</Button>
|
||||
</DialogAction>
|
||||
<DialogAction
|
||||
title="Stop Services"
|
||||
description={`Are you sure you want to stop ${selectedServices.length} services?`}
|
||||
type="destructive"
|
||||
onClick={handleBulkStop}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full justify-start text-destructive"
|
||||
>
|
||||
<Ban className="mr-2 h-4 w-4" />
|
||||
Stop
|
||||
</Button>
|
||||
</DialogAction>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<Popover open={openCombobox} onOpenChange={setOpenCombobox}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
aria-expanded={openCombobox}
|
||||
className="min-w-[200px] justify-between"
|
||||
>
|
||||
{selectedTypes.length === 0
|
||||
? "Select types..."
|
||||
: `${selectedTypes.length} selected`}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search type..." />
|
||||
<CommandEmpty>No type found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{serviceTypes.map((type) => (
|
||||
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:gap-4 sm:items-center">
|
||||
<div className="w-full relative">
|
||||
<Input
|
||||
placeholder="Filter services..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pr-10"
|
||||
/>
|
||||
<Search className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||
</div>
|
||||
<Popover
|
||||
open={openCombobox}
|
||||
onOpenChange={setOpenCombobox}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
aria-expanded={openCombobox}
|
||||
className="min-w-[200px] justify-between"
|
||||
>
|
||||
{selectedTypes.length === 0
|
||||
? "Select types..."
|
||||
: `${selectedTypes.length} selected`}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search type..." />
|
||||
<CommandEmpty>No type found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{serviceTypes.map((type) => (
|
||||
<CommandItem
|
||||
key={type.value}
|
||||
onSelect={() => {
|
||||
setSelectedTypes((prev) =>
|
||||
prev.includes(type.value)
|
||||
? prev.filter((t) => t !== type.value)
|
||||
: [...prev, type.value],
|
||||
);
|
||||
setOpenCombobox(false);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
selectedTypes.includes(type.value)
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{type.icon && (
|
||||
<type.icon className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{type.label}
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
<CommandItem
|
||||
key={type.value}
|
||||
onSelect={() => {
|
||||
setSelectedTypes((prev) =>
|
||||
prev.includes(type.value)
|
||||
? prev.filter((t) => t !== type.value)
|
||||
: [...prev, type.value],
|
||||
);
|
||||
setSelectedTypes([]);
|
||||
setOpenCombobox(false);
|
||||
}}
|
||||
className="border-t"
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
selectedTypes.includes(type.value)
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{type.icon && (
|
||||
<type.icon className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{type.label}
|
||||
<div className="flex flex-row items-center">
|
||||
<X className="mr-2 h-4 w-4" />
|
||||
Clear filters
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
setSelectedTypes([]);
|
||||
setOpenCombobox(false);
|
||||
}}
|
||||
className="border-t"
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<X className="mr-2 h-4 w-4" />
|
||||
Clear filters
|
||||
</div>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full gap-8">
|
||||
@@ -408,6 +562,27 @@ const Project = (
|
||||
<StatusTooltip status={service.status} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"absolute -left-3 -bottom-3 size-9 translate-y-1 rounded-full p-0 transition-all duration-200 z-10 bg-background border",
|
||||
selectedServices.includes(service.id)
|
||||
? "opacity-100 translate-y-0"
|
||||
: "opacity-0 group-hover:translate-y-0 group-hover:opacity-100",
|
||||
)}
|
||||
onClick={(e) =>
|
||||
handleServiceSelect(service.id, e)
|
||||
}
|
||||
>
|
||||
<div className="h-full w-full flex items-center justify-center">
|
||||
<Checkbox
|
||||
checked={selectedServices.includes(
|
||||
service.id,
|
||||
)}
|
||||
className="data-[state=checked]:bg-primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<div className="flex flex-row items-center gap-2 justify-between w-full">
|
||||
@@ -482,7 +657,7 @@ export async function getServerSideProps(
|
||||
const { params } = ctx;
|
||||
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -498,8 +673,8 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
@@ -516,7 +691,7 @@ export async function getServerSideProps(
|
||||
projectId: params?.projectId,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -13,13 +13,13 @@ import { ShowGeneralApplication } from "@/components/dashboard/application/gener
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { ShowPreviewDeployments } from "@/components/dashboard/application/preview-deployments/show-preview-deployments";
|
||||
import { UpdateApplication } from "@/components/dashboard/application/update-application";
|
||||
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring";
|
||||
import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-container-monitoring";
|
||||
import { ProjectLayout } from "@/components/layouts/project-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -38,9 +38,10 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { GlobeIcon, HelpCircle, ServerOff, Trash2 } from "lucide-react";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { GlobeIcon, HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
InferGetServerSidePropsType,
|
||||
@@ -48,7 +49,7 @@ import type {
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useState, useEffect, type ReactElement } from "react";
|
||||
import { type ReactElement, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import superjson from "superjson";
|
||||
|
||||
@@ -64,6 +65,7 @@ type TabState =
|
||||
const Service = (
|
||||
props: InferGetServerSidePropsType<typeof getServerSideProps>,
|
||||
) => {
|
||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||
const { applicationId, activeTab } = props;
|
||||
const router = useRouter();
|
||||
const { projectId } = router.query;
|
||||
@@ -82,17 +84,8 @@ const Service = (
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync, isLoading: isRemoving } =
|
||||
api.application.delete.useMutation();
|
||||
const { data: auth } = api.auth.get.useQuery();
|
||||
const { data: user } = api.user.byAuthId.useQuery(
|
||||
{
|
||||
authId: auth?.id || "",
|
||||
},
|
||||
{
|
||||
enabled: !!auth?.id && auth?.rol === "user",
|
||||
},
|
||||
);
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
|
||||
return (
|
||||
<div className="pb-10">
|
||||
@@ -140,6 +133,13 @@ const Service = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
if (data?.server?.ipAddress) {
|
||||
copy(data.server.ipAddress);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
@@ -176,35 +176,8 @@ const Service = (
|
||||
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdateApplication applicationId={applicationId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Delete Application"
|
||||
description="Are you sure you want to delete this application?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
applicationId: applicationId,
|
||||
})
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Application deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting application");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
{(auth?.role === "owner" || auth?.canDeleteServices) && (
|
||||
<DeleteService id={applicationId} type="application" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -246,12 +219,16 @@ const Service = (
|
||||
<TabsList
|
||||
className={cn(
|
||||
"flex gap-8 justify-start max-xl:overflow-x-scroll overflow-y-hidden",
|
||||
data?.serverId ? "md:grid-cols-7" : "md:grid-cols-8",
|
||||
isCloud && data?.serverId
|
||||
? "md:grid-cols-7"
|
||||
: data?.serverId
|
||||
? "md:grid-cols-6"
|
||||
: "md:grid-cols-7",
|
||||
)}
|
||||
>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||
{!data?.serverId && (
|
||||
{((data?.serverId && isCloud) || !data?.server) && (
|
||||
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="logs">Logs</TabsTrigger>
|
||||
@@ -274,13 +251,54 @@ const Service = (
|
||||
<ShowEnvironment applicationId={applicationId} />
|
||||
</div>
|
||||
</TabsContent>
|
||||
{!data?.serverId && (
|
||||
<TabsContent value="monitoring">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<DockerMonitoring appName={data?.appName || ""} />
|
||||
|
||||
<TabsContent value="monitoring">
|
||||
<div className="pt-2.5">
|
||||
<div className="flex flex-col gap-4 border rounded-lg p-6">
|
||||
{data?.serverId && isCloud ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`${data?.serverId ? `http://${data?.server?.ipAddress}:${data?.server?.metricsConfig?.server?.port}` : "http://localhost:4500"}`}
|
||||
token={
|
||||
data?.server?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{/* {monitoring?.enabledFeatures &&
|
||||
isCloud &&
|
||||
data?.serverId && (
|
||||
<div className="flex flex-row border w-fit p-4 rounded-lg items-center gap-2">
|
||||
<Label className="text-muted-foreground">
|
||||
Change Monitoring
|
||||
</Label>
|
||||
<Switch
|
||||
checked={toggleMonitoring}
|
||||
onCheckedChange={setToggleMonitoring}
|
||||
/>
|
||||
</div>
|
||||
)} */}
|
||||
|
||||
{/* {toggleMonitoring ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`http://${monitoring?.serverIp}:${monitoring?.metricsConfig?.server?.port}`}
|
||||
token={
|
||||
monitoring?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : ( */}
|
||||
<div>
|
||||
<ContainerFreeMonitoring
|
||||
appName={data?.appName || ""}
|
||||
/>
|
||||
</div>
|
||||
{/* )} */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="logs">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
@@ -342,7 +360,7 @@ export async function getServerSideProps(
|
||||
const { query, params, req, res } = ctx;
|
||||
|
||||
const activeTab = query.tab;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -358,8 +376,8 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
@@ -371,6 +389,8 @@ export async function getServerSideProps(
|
||||
applicationId: params?.applicationId,
|
||||
});
|
||||
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
@@ -378,7 +398,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command";
|
||||
import { DeleteCompose } from "@/components/dashboard/compose/delete-compose";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowDeploymentsCompose } from "@/components/dashboard/compose/deployments/show-deployments-compose";
|
||||
import { ShowDomainsCompose } from "@/components/dashboard/compose/domains/show-domains";
|
||||
import { ShowGeneralCompose } from "@/components/dashboard/compose/general/show";
|
||||
import { ShowDockerLogsCompose } from "@/components/dashboard/compose/logs/show";
|
||||
import { ShowDockerLogsStack } from "@/components/dashboard/compose/logs/show-stack";
|
||||
import { ShowMonitoringCompose } from "@/components/dashboard/compose/monitoring/show";
|
||||
import { UpdateCompose } from "@/components/dashboard/compose/update-compose";
|
||||
import { ComposeFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-compose-monitoring";
|
||||
import { ComposePaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-compose-monitoring";
|
||||
import { ProjectLayout } from "@/components/layouts/project-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
@@ -31,8 +32,9 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { CircuitBoard, ServerOff } from "lucide-react";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import type {
|
||||
@@ -42,7 +44,8 @@ import type {
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useState, useEffect, type ReactElement } from "react";
|
||||
import { type ReactElement, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import superjson from "superjson";
|
||||
|
||||
type TabState =
|
||||
@@ -56,6 +59,7 @@ type TabState =
|
||||
const Service = (
|
||||
props: InferGetServerSidePropsType<typeof getServerSideProps>,
|
||||
) => {
|
||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||
const { composeId, activeTab } = props;
|
||||
const router = useRouter();
|
||||
const { projectId } = router.query;
|
||||
@@ -74,15 +78,8 @@ const Service = (
|
||||
},
|
||||
);
|
||||
|
||||
const { data: auth } = api.auth.get.useQuery();
|
||||
const { data: user } = api.user.byAuthId.useQuery(
|
||||
{
|
||||
authId: auth?.id || "",
|
||||
},
|
||||
{
|
||||
enabled: !!auth?.id && auth?.rol === "user",
|
||||
},
|
||||
);
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
|
||||
return (
|
||||
<div className="pb-10">
|
||||
@@ -131,6 +128,13 @@ const Service = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
if (data?.server?.ipAddress) {
|
||||
copy(data.server.ipAddress);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
@@ -142,7 +146,7 @@ const Service = (
|
||||
{data?.server?.name || "Dokploy Server"}
|
||||
</Badge>
|
||||
{data?.server?.serverStatus === "inactive" && (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Label className="break-all w-fit flex flex-row gap-1 items-center">
|
||||
@@ -167,8 +171,8 @@ const Service = (
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdateCompose composeId={composeId} />
|
||||
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DeleteCompose composeId={composeId} />
|
||||
{(auth?.role === "owner" || auth?.canDeleteServices) && (
|
||||
<DeleteService id={composeId} type="compose" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -211,22 +215,16 @@ const Service = (
|
||||
<TabsList
|
||||
className={cn(
|
||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||
data?.serverId ? "md:grid-cols-6" : "md:grid-cols-7",
|
||||
data?.composeType === "docker-compose"
|
||||
? ""
|
||||
: "md:grid-cols-6",
|
||||
data?.serverId && data?.composeType === "stack"
|
||||
? "md:grid-cols-5"
|
||||
: "",
|
||||
isCloud && data?.serverId
|
||||
? "md:grid-cols-7"
|
||||
: data?.serverId
|
||||
? "md:grid-cols-6"
|
||||
: "md:grid-cols-7",
|
||||
)}
|
||||
>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
{data?.composeType === "docker-compose" && (
|
||||
<TabsTrigger value="environment">
|
||||
Environment
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{!data?.serverId && (
|
||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||
{((data?.serverId && isCloud) || !data?.server) && (
|
||||
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="logs">Logs</TabsTrigger>
|
||||
@@ -246,17 +244,59 @@ const Service = (
|
||||
<ShowEnvironment id={composeId} type="compose" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
{!data?.serverId && (
|
||||
<TabsContent value="monitoring">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<ShowMonitoringCompose
|
||||
serverId={data?.serverId || ""}
|
||||
appName={data?.appName || ""}
|
||||
appType={data?.composeType || "docker-compose"}
|
||||
/>
|
||||
|
||||
<TabsContent value="monitoring">
|
||||
<div className="pt-2.5">
|
||||
<div className="flex flex-col border rounded-lg ">
|
||||
{data?.serverId && isCloud ? (
|
||||
<ComposePaidMonitoring
|
||||
serverId={data?.serverId || ""}
|
||||
baseUrl={`${data?.serverId ? `http://${data?.server?.ipAddress}:${data?.server?.metricsConfig?.server?.port}` : "http://localhost:4500"}`}
|
||||
appName={data?.appName || ""}
|
||||
token={
|
||||
data?.server?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
appType={data?.composeType || "docker-compose"}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{/* {monitoring?.enabledFeatures &&
|
||||
isCloud &&
|
||||
data?.serverId && (
|
||||
<div className="flex flex-row border w-fit p-4 rounded-lg items-center gap-2 m-4">
|
||||
<Label className="text-muted-foreground">
|
||||
Change Monitoring
|
||||
</Label>
|
||||
<Switch
|
||||
checked={toggleMonitoring}
|
||||
onCheckedChange={setToggleMonitoring}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{toggleMonitoring ? (
|
||||
<ComposePaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`http://${monitoring?.serverIp}:${monitoring?.metricsConfig?.server?.port}`}
|
||||
token={
|
||||
monitoring?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
appType={data?.composeType || "docker-compose"}
|
||||
/>
|
||||
) : ( */}
|
||||
{/* <div> */}
|
||||
<ComposeFreeMonitoring
|
||||
serverId={data?.serverId || ""}
|
||||
appName={data?.appName || ""}
|
||||
appType={data?.composeType || "docker-compose"}
|
||||
/>
|
||||
{/* </div> */}
|
||||
{/* )} */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="logs">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
@@ -316,7 +356,7 @@ export async function getServerSideProps(
|
||||
const { query, params, req, res } = ctx;
|
||||
|
||||
const activeTab = query.tab;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -332,8 +372,8 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
@@ -344,7 +384,7 @@ export async function getServerSideProps(
|
||||
await helpers.compose.one.fetch({
|
||||
composeId: params?.composeId,
|
||||
});
|
||||
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
@@ -352,7 +392,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -2,20 +2,20 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
import { ShowExternalMariadbCredentials } from "@/components/dashboard/mariadb/general/show-external-mariadb-credentials";
|
||||
import { ShowGeneralMariadb } from "@/components/dashboard/mariadb/general/show-general-mariadb";
|
||||
import { ShowInternalMariadbCredentials } from "@/components/dashboard/mariadb/general/show-internal-mariadb-credentials";
|
||||
import { UpdateMariadb } from "@/components/dashboard/mariadb/update-mariadb";
|
||||
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
|
||||
import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring";
|
||||
import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-container-monitoring";
|
||||
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
|
||||
import { MariadbIcon } from "@/components/icons/data-tools-icons";
|
||||
import { ProjectLayout } from "@/components/layouts/project-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -34,9 +34,9 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { HelpCircle, ServerOff, Trash2 } from "lucide-react";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
InferGetServerSidePropsType,
|
||||
@@ -44,8 +44,7 @@ import type {
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useState, type ReactElement } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
|
||||
@@ -53,22 +52,17 @@ type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
|
||||
const Mariadb = (
|
||||
props: InferGetServerSidePropsType<typeof getServerSideProps>,
|
||||
) => {
|
||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||
|
||||
const { mariadbId, activeTab } = props;
|
||||
const router = useRouter();
|
||||
const { projectId } = router.query;
|
||||
const [tab, setSab] = useState<TabState>(activeTab);
|
||||
const { data } = api.mariadb.one.useQuery({ mariadbId });
|
||||
const { data: auth } = api.auth.get.useQuery();
|
||||
const { data: user } = api.user.byAuthId.useQuery(
|
||||
{
|
||||
authId: auth?.id || "",
|
||||
},
|
||||
{
|
||||
enabled: !!auth?.id && auth?.rol === "user",
|
||||
},
|
||||
);
|
||||
const { mutateAsync: remove, isLoading: isRemoving } =
|
||||
api.mariadb.remove.useMutation();
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<BreadcrumbSidebar
|
||||
@@ -148,35 +142,10 @@ const Mariadb = (
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdateMariadb mariadbId={mariadbId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Remove Mariadb"
|
||||
description="Are you sure you want to delete this mariadb?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await remove({ mariadbId })
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Mariadb deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the mariadb");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
{(auth?.role === "owner" || auth?.canDeleteServices) && (
|
||||
<DeleteService id={mariadbId} type="mariadb" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,12 +188,16 @@ const Mariadb = (
|
||||
<TabsList
|
||||
className={cn(
|
||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||
data?.serverId ? "md:grid-cols-5" : "md:grid-cols-6",
|
||||
isCloud && data?.serverId
|
||||
? "md:grid-cols-6"
|
||||
: data?.serverId
|
||||
? "md:grid-cols-5"
|
||||
: "md:grid-cols-6",
|
||||
)}
|
||||
>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||
{!data?.serverId && (
|
||||
{((data?.serverId && isCloud) || !data?.server) && (
|
||||
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="backups">Backups</TabsTrigger>
|
||||
@@ -245,13 +218,51 @@ const Mariadb = (
|
||||
<ShowEnvironment id={mariadbId} type="mariadb" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
{!data?.serverId && (
|
||||
<TabsContent value="monitoring">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<DockerMonitoring appName={data?.appName || ""} />
|
||||
<TabsContent value="monitoring">
|
||||
<div className="pt-2.5">
|
||||
<div className="flex flex-col gap-4 border rounded-lg p-6">
|
||||
{data?.serverId && isCloud ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`${data?.serverId ? `http://${data?.server?.ipAddress}:${data?.server?.metricsConfig?.server?.port}` : "http://localhost:4500"}`}
|
||||
token={
|
||||
data?.server?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{/* {monitoring?.enabledFeatures && (
|
||||
<div className="flex flex-row border w-fit p-4 rounded-lg items-center gap-2">
|
||||
<Label className="text-muted-foreground">
|
||||
Change Monitoring
|
||||
</Label>
|
||||
<Switch
|
||||
checked={toggleMonitoring}
|
||||
onCheckedChange={setToggleMonitoring}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{toggleMonitoring ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`http://${monitoring?.serverIp}:${monitoring?.metricsConfig?.server?.port}`}
|
||||
token={
|
||||
monitoring?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div> */}
|
||||
<ContainerFreeMonitoring
|
||||
appName={data?.appName || ""}
|
||||
/>
|
||||
{/* </div> */}
|
||||
{/* )} */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="logs">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<ShowDockerLogs
|
||||
@@ -295,7 +306,7 @@ export async function getServerSideProps(
|
||||
const { query, params, req, res } = ctx;
|
||||
const activeTab = query.tab;
|
||||
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -311,8 +322,8 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
@@ -322,7 +333,7 @@ export async function getServerSideProps(
|
||||
await helpers.mariadb.one.fetch({
|
||||
mariadbId: params?.mariadbId,
|
||||
});
|
||||
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
@@ -330,7 +341,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -2,21 +2,20 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
import { ShowExternalMongoCredentials } from "@/components/dashboard/mongo/general/show-external-mongo-credentials";
|
||||
import { ShowGeneralMongo } from "@/components/dashboard/mongo/general/show-general-mongo";
|
||||
import { ShowInternalMongoCredentials } from "@/components/dashboard/mongo/general/show-internal-mongo-credentials";
|
||||
import { UpdateMongo } from "@/components/dashboard/mongo/update-mongo";
|
||||
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
|
||||
import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring";
|
||||
import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-container-monitoring";
|
||||
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
|
||||
import { MongodbIcon } from "@/components/icons/data-tools-icons";
|
||||
import { ProjectLayout } from "@/components/layouts/project-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -35,9 +34,9 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { HelpCircle, ServerOff, Trash2 } from "lucide-react";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
InferGetServerSidePropsType,
|
||||
@@ -45,8 +44,7 @@ import type {
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useState, type ReactElement } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
|
||||
@@ -54,23 +52,16 @@ type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
|
||||
const Mongo = (
|
||||
props: InferGetServerSidePropsType<typeof getServerSideProps>,
|
||||
) => {
|
||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||
const { mongoId, activeTab } = props;
|
||||
const router = useRouter();
|
||||
const { projectId } = router.query;
|
||||
const [tab, setSab] = useState<TabState>(activeTab);
|
||||
const { data } = api.mongo.one.useQuery({ mongoId });
|
||||
|
||||
const { data: auth } = api.auth.get.useQuery();
|
||||
const { data: user } = api.user.byAuthId.useQuery(
|
||||
{
|
||||
authId: auth?.id || "",
|
||||
},
|
||||
{
|
||||
enabled: !!auth?.id && auth?.rol === "user",
|
||||
},
|
||||
);
|
||||
const { mutateAsync: remove, isLoading: isRemoving } =
|
||||
api.mongo.remove.useMutation();
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
|
||||
return (
|
||||
<div className="pb-10">
|
||||
@@ -154,33 +145,8 @@ const Mongo = (
|
||||
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdateMongo mongoId={mongoId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Remove mongo"
|
||||
description="Are you sure you want to delete this mongo?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await remove({ mongoId })
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Mongo deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the mongo");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
{(auth?.role === "owner" || auth?.canDeleteServices) && (
|
||||
<DeleteService id={mongoId} type="mongo" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -223,12 +189,16 @@ const Mongo = (
|
||||
<TabsList
|
||||
className={cn(
|
||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||
data?.serverId ? "md:grid-cols-5" : "md:grid-cols-6",
|
||||
isCloud && data?.serverId
|
||||
? "md:grid-cols-6"
|
||||
: data?.serverId
|
||||
? "md:grid-cols-5"
|
||||
: "md:grid-cols-6",
|
||||
)}
|
||||
>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||
{!data?.serverId && (
|
||||
{((data?.serverId && isCloud) || !data?.server) && (
|
||||
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="backups">Backups</TabsTrigger>
|
||||
@@ -249,13 +219,51 @@ const Mongo = (
|
||||
<ShowEnvironment id={mongoId} type="mongo" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
{!data?.serverId && (
|
||||
<TabsContent value="monitoring">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<DockerMonitoring appName={data?.appName || ""} />
|
||||
<TabsContent value="monitoring">
|
||||
<div className="pt-2.5">
|
||||
<div className="flex flex-col gap-4 border rounded-lg p-6">
|
||||
{data?.serverId && isCloud ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`${data?.serverId ? `http://${data?.server?.ipAddress}:${data?.server?.metricsConfig?.server?.port}` : "http://localhost:4500"}`}
|
||||
token={
|
||||
data?.server?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{/* {monitoring?.enabledFeatures && (
|
||||
<div className="flex flex-row border w-fit p-4 rounded-lg items-center gap-2">
|
||||
<Label className="text-muted-foreground">
|
||||
Change Monitoring
|
||||
</Label>
|
||||
<Switch
|
||||
checked={toggleMonitoring}
|
||||
onCheckedChange={setToggleMonitoring}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{toggleMonitoring ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`http://${monitoring?.serverIp}:${monitoring?.metricsConfig?.server?.port}`}
|
||||
token={
|
||||
monitoring?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div> */}
|
||||
<ContainerFreeMonitoring
|
||||
appName={data?.appName || ""}
|
||||
/>
|
||||
{/* </div> */}
|
||||
{/* )} */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="logs">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<ShowDockerLogs
|
||||
@@ -299,7 +307,7 @@ export async function getServerSideProps(
|
||||
const { query, params, req, res } = ctx;
|
||||
const activeTab = query.tab;
|
||||
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -315,8 +323,8 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
@@ -326,7 +334,7 @@ export async function getServerSideProps(
|
||||
await helpers.mongo.one.fetch({
|
||||
mongoId: params?.mongoId,
|
||||
});
|
||||
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
@@ -334,7 +342,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -2,8 +2,10 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
|
||||
import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring";
|
||||
import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-container-monitoring";
|
||||
import { ShowExternalMysqlCredentials } from "@/components/dashboard/mysql/general/show-external-mysql-credentials";
|
||||
import { ShowGeneralMysql } from "@/components/dashboard/mysql/general/show-general-mysql";
|
||||
import { ShowInternalMysqlCredentials } from "@/components/dashboard/mysql/general/show-internal-mysql-credentials";
|
||||
@@ -12,10 +14,8 @@ import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show
|
||||
import { MysqlIcon } from "@/components/icons/data-tools-icons";
|
||||
import { ProjectLayout } from "@/components/layouts/project-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -34,9 +34,9 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { HelpCircle, ServerOff, Trash2 } from "lucide-react";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
InferGetServerSidePropsType,
|
||||
@@ -44,8 +44,7 @@ import type {
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useState, type ReactElement } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
|
||||
@@ -53,23 +52,16 @@ type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
|
||||
const MySql = (
|
||||
props: InferGetServerSidePropsType<typeof getServerSideProps>,
|
||||
) => {
|
||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||
const { mysqlId, activeTab } = props;
|
||||
const router = useRouter();
|
||||
const { projectId } = router.query;
|
||||
const [tab, setSab] = useState<TabState>(activeTab);
|
||||
const { data } = api.mysql.one.useQuery({ mysqlId });
|
||||
const { data: auth } = api.auth.get.useQuery();
|
||||
const { data: user } = api.user.byAuthId.useQuery(
|
||||
{
|
||||
authId: auth?.id || "",
|
||||
},
|
||||
{
|
||||
enabled: !!auth?.id && auth?.rol === "user",
|
||||
},
|
||||
);
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
|
||||
const { mutateAsync: remove, isLoading: isRemoving } =
|
||||
api.mysql.remove.useMutation();
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<BreadcrumbSidebar
|
||||
@@ -153,33 +145,8 @@ const MySql = (
|
||||
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdateMysql mysqlId={mysqlId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Remove Mysql"
|
||||
description="Are you sure you want to delete this mysql?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await remove({ mysqlId })
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Mysql deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the mysql");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
{(auth?.role === "owner" || auth?.canDeleteServices) && (
|
||||
<DeleteService id={mysqlId} type="mysql" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,15 +188,19 @@ const MySql = (
|
||||
<div className="flex flex-row items-center justify-between w-full gap-4">
|
||||
<TabsList
|
||||
className={cn(
|
||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||
data?.serverId ? "md:grid-cols-5" : "md:grid-cols-6",
|
||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start ",
|
||||
isCloud && data?.serverId
|
||||
? "md:grid-cols-6"
|
||||
: data?.serverId
|
||||
? "md:grid-cols-5"
|
||||
: "md:grid-cols-6",
|
||||
)}
|
||||
>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="environment">
|
||||
Environment
|
||||
</TabsTrigger>
|
||||
{!data?.serverId && (
|
||||
{((data?.serverId && isCloud) || !data?.server) && (
|
||||
<TabsTrigger value="monitoring">
|
||||
Monitoring
|
||||
</TabsTrigger>
|
||||
@@ -252,13 +223,51 @@ const MySql = (
|
||||
<ShowEnvironment id={mysqlId} type="mysql" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
{!data?.serverId && (
|
||||
<TabsContent value="monitoring">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<DockerMonitoring appName={data?.appName || ""} />
|
||||
<TabsContent value="monitoring">
|
||||
<div className="pt-2.5">
|
||||
<div className="flex flex-col gap-4 border rounded-lg p-6">
|
||||
{data?.serverId && isCloud ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`${data?.serverId ? `http://${data?.server?.ipAddress}:${data?.server?.metricsConfig?.server?.port}` : "http://localhost:4500"}`}
|
||||
token={
|
||||
data?.server?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{/* {monitoring?.enabledFeatures && (
|
||||
<div className="flex flex-row border w-fit p-4 rounded-lg items-center gap-2">
|
||||
<Label className="text-muted-foreground">
|
||||
Change Monitoring
|
||||
</Label>
|
||||
<Switch
|
||||
checked={toggleMonitoring}
|
||||
onCheckedChange={setToggleMonitoring}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{toggleMonitoring ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`http://${monitoring?.serverIp}:${monitoring?.metricsConfig?.server?.port}`}
|
||||
token={
|
||||
monitoring?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div> */}
|
||||
<ContainerFreeMonitoring
|
||||
appName={data?.appName || ""}
|
||||
/>
|
||||
{/* </div> */}
|
||||
{/* )} */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="logs">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<ShowDockerLogs
|
||||
@@ -303,7 +312,7 @@ export async function getServerSideProps(
|
||||
const { query, params, req, res } = ctx;
|
||||
const activeTab = query.tab;
|
||||
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -319,8 +328,8 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
@@ -330,7 +339,7 @@ export async function getServerSideProps(
|
||||
await helpers.mysql.one.fetch({
|
||||
mysqlId: params?.mysqlId,
|
||||
});
|
||||
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
@@ -338,7 +347,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -2,8 +2,10 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
|
||||
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
|
||||
import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring";
|
||||
import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-container-monitoring";
|
||||
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
|
||||
import { ShowExternalPostgresCredentials } from "@/components/dashboard/postgres/general/show-external-postgres-credentials";
|
||||
import { ShowGeneralPostgres } from "@/components/dashboard/postgres/general/show-general-postgres";
|
||||
@@ -12,11 +14,8 @@ import { UpdatePostgres } from "@/components/dashboard/postgres/update-postgres"
|
||||
import { PostgresqlIcon } from "@/components/icons/data-tools-icons";
|
||||
import { ProjectLayout } from "@/components/layouts/project-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -35,9 +34,9 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { HelpCircle, ServerOff, Trash2 } from "lucide-react";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
InferGetServerSidePropsType,
|
||||
@@ -45,8 +44,7 @@ import type {
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useState, type ReactElement } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
|
||||
@@ -54,24 +52,15 @@ type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
|
||||
const Postgresql = (
|
||||
props: InferGetServerSidePropsType<typeof getServerSideProps>,
|
||||
) => {
|
||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||
const { postgresId, activeTab } = props;
|
||||
const router = useRouter();
|
||||
const { projectId } = router.query;
|
||||
const [tab, setSab] = useState<TabState>(activeTab);
|
||||
const { data } = api.postgres.one.useQuery({ postgresId });
|
||||
const { data: auth } = api.auth.get.useQuery();
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
|
||||
const { data: user } = api.user.byAuthId.useQuery(
|
||||
{
|
||||
authId: auth?.id || "",
|
||||
},
|
||||
{
|
||||
enabled: !!auth?.id && auth?.rol === "user",
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync: remove, isLoading: isRemoving } =
|
||||
api.postgres.remove.useMutation();
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
|
||||
return (
|
||||
<div className="pb-10">
|
||||
@@ -155,33 +144,8 @@ const Postgresql = (
|
||||
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdatePostgres postgresId={postgresId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Remove Postgres"
|
||||
description="Are you sure you want to delete this postgres?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await remove({ postgresId })
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Postgres deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the postgres");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
{(auth?.role === "owner" || auth?.canDeleteServices) && (
|
||||
<DeleteService id={postgresId} type="postgres" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -224,12 +188,16 @@ const Postgresql = (
|
||||
<TabsList
|
||||
className={cn(
|
||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||
data?.serverId ? "md:grid-cols-5" : "md:grid-cols-6",
|
||||
isCloud && data?.serverId
|
||||
? "md:grid-cols-6"
|
||||
: data?.serverId
|
||||
? "md:grid-cols-5"
|
||||
: "md:grid-cols-6",
|
||||
)}
|
||||
>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||
{!data?.serverId && (
|
||||
{((data?.serverId && isCloud) || !data?.server) && (
|
||||
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="backups">Backups</TabsTrigger>
|
||||
@@ -254,13 +222,51 @@ const Postgresql = (
|
||||
<ShowEnvironment id={postgresId} type="postgres" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
{!data?.serverId && (
|
||||
<TabsContent value="monitoring">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<DockerMonitoring appName={data?.appName || ""} />
|
||||
<TabsContent value="monitoring">
|
||||
<div className="pt-2.5">
|
||||
<div className="flex flex-col gap-4 border rounded-lg p-6">
|
||||
{data?.serverId && isCloud ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`${data?.serverId ? `http://${data?.server?.ipAddress}:${data?.server?.metricsConfig?.server?.port}` : "http://localhost:4500"}`}
|
||||
token={
|
||||
data?.server?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{/* {monitoring?.enabledFeatures && (
|
||||
<div className="flex flex-row border w-fit p-4 rounded-lg items-center gap-2">
|
||||
<Label className="text-muted-foreground">
|
||||
Change Monitoring
|
||||
</Label>
|
||||
<Switch
|
||||
checked={toggleMonitoring}
|
||||
onCheckedChange={setToggleMonitoring}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{toggleMonitoring ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`http://${monitoring?.serverIp}:${monitoring?.metricsConfig?.server?.port}`}
|
||||
token={
|
||||
monitoring?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div> */}
|
||||
<ContainerFreeMonitoring
|
||||
appName={data?.appName || ""}
|
||||
/>
|
||||
{/* </div> */}
|
||||
{/* )} */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="logs">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<ShowDockerLogs
|
||||
@@ -303,7 +309,7 @@ export async function getServerSideProps(
|
||||
) {
|
||||
const { query, params, req, res } = ctx;
|
||||
const activeTab = query.tab;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -319,8 +325,8 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
@@ -330,7 +336,7 @@ export async function getServerSideProps(
|
||||
await helpers.postgres.one.fetch({
|
||||
postgresId: params?.postgresId,
|
||||
});
|
||||
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
@@ -338,7 +344,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -2,7 +2,9 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
|
||||
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
|
||||
import { DeleteService } from "@/components/dashboard/compose/delete-service";
|
||||
import { ContainerFreeMonitoring } from "@/components/dashboard/monitoring/free/container/show-free-container-monitoring";
|
||||
import { ContainerPaidMonitoring } from "@/components/dashboard/monitoring/paid/container/show-paid-container-monitoring";
|
||||
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
|
||||
import { ShowExternalRedisCredentials } from "@/components/dashboard/redis/general/show-external-redis-credentials";
|
||||
import { ShowGeneralRedis } from "@/components/dashboard/redis/general/show-general-redis";
|
||||
@@ -11,10 +13,8 @@ import { UpdateRedis } from "@/components/dashboard/redis/update-redis";
|
||||
import { RedisIcon } from "@/components/icons/data-tools-icons";
|
||||
import { ProjectLayout } from "@/components/layouts/project-layout";
|
||||
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -33,9 +33,9 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { HelpCircle, ServerOff, Trash2 } from "lucide-react";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
InferGetServerSidePropsType,
|
||||
@@ -43,8 +43,7 @@ import type {
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useState, type ReactElement } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
type TabState = "projects" | "monitoring" | "settings" | "advanced";
|
||||
@@ -52,24 +51,17 @@ type TabState = "projects" | "monitoring" | "settings" | "advanced";
|
||||
const Redis = (
|
||||
props: InferGetServerSidePropsType<typeof getServerSideProps>,
|
||||
) => {
|
||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||
const { redisId, activeTab } = props;
|
||||
const router = useRouter();
|
||||
const { projectId } = router.query;
|
||||
const [tab, setSab] = useState<TabState>(activeTab);
|
||||
const { data } = api.redis.one.useQuery({ redisId });
|
||||
|
||||
const { data: auth } = api.auth.get.useQuery();
|
||||
const { data: user } = api.user.byAuthId.useQuery(
|
||||
{
|
||||
authId: auth?.id || "",
|
||||
},
|
||||
{
|
||||
enabled: !!auth?.id && auth?.rol === "user",
|
||||
},
|
||||
);
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
|
||||
const { mutateAsync: remove, isLoading: isRemoving } =
|
||||
api.redis.remove.useMutation();
|
||||
return (
|
||||
<div className="pb-10">
|
||||
<BreadcrumbSidebar
|
||||
@@ -152,33 +144,8 @@ const Redis = (
|
||||
|
||||
<div className="flex flex-row gap-2 justify-end">
|
||||
<UpdateRedis redisId={redisId} />
|
||||
{(auth?.rol === "admin" || user?.canDeleteServices) && (
|
||||
<DialogAction
|
||||
title="Remove Redis"
|
||||
description="Are you sure you want to delete this redis?"
|
||||
type="destructive"
|
||||
onClick={async () => {
|
||||
await remove({ redisId })
|
||||
.then(() => {
|
||||
router.push(
|
||||
`/dashboard/project/${data?.projectId}`,
|
||||
);
|
||||
toast.success("Redis deleted successfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error deleting the redis");
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="group hover:bg-red-500/10 "
|
||||
isLoading={isRemoving}
|
||||
>
|
||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||
</Button>
|
||||
</DialogAction>
|
||||
{(auth?.role === "owner" || auth?.canDeleteServices) && (
|
||||
<DeleteService id={redisId} type="redis" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,12 +188,16 @@ const Redis = (
|
||||
<TabsList
|
||||
className={cn(
|
||||
"md:grid md:w-fit max-md:overflow-y-scroll justify-start",
|
||||
data?.serverId ? "md:grid-cols-4" : "md:grid-cols-5",
|
||||
isCloud && data?.serverId
|
||||
? "md:grid-cols-5"
|
||||
: data?.serverId
|
||||
? "md:grid-cols-4"
|
||||
: "md:grid-cols-5",
|
||||
)}
|
||||
>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||
{!data?.serverId && (
|
||||
{((data?.serverId && isCloud) || !data?.server) && (
|
||||
<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="logs">Logs</TabsTrigger>
|
||||
@@ -246,13 +217,51 @@ const Redis = (
|
||||
<ShowEnvironment id={redisId} type="redis" />
|
||||
</div>
|
||||
</TabsContent>
|
||||
{!data?.serverId && (
|
||||
<TabsContent value="monitoring">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<DockerMonitoring appName={data?.appName || ""} />
|
||||
<TabsContent value="monitoring">
|
||||
<div className="pt-2.5">
|
||||
<div className="flex flex-col gap-4 border rounded-lg p-6">
|
||||
{data?.serverId && isCloud ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`${data?.serverId ? `http://${data?.server?.ipAddress}:${data?.server?.metricsConfig?.server?.port}` : "http://localhost:4500"}`}
|
||||
token={
|
||||
data?.server?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{/* {monitoring?.enabledFeatures && (
|
||||
<div className="flex flex-row border w-fit p-4 rounded-lg items-center gap-2">
|
||||
<Label className="text-muted-foreground">
|
||||
Change Monitoring
|
||||
</Label>
|
||||
<Switch
|
||||
checked={toggleMonitoring}
|
||||
onCheckedChange={setToggleMonitoring}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{toggleMonitoring ? (
|
||||
<ContainerPaidMonitoring
|
||||
appName={data?.appName || ""}
|
||||
baseUrl={`http://${monitoring?.serverIp}:${monitoring?.metricsConfig?.server?.port}`}
|
||||
token={
|
||||
monitoring?.metricsConfig?.server?.token || ""
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div> */}
|
||||
<ContainerFreeMonitoring
|
||||
appName={data?.appName || ""}
|
||||
/>
|
||||
{/* </div> */}
|
||||
{/* )} */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="logs">
|
||||
<div className="flex flex-col gap-4 pt-2.5">
|
||||
<ShowDockerLogs
|
||||
@@ -291,7 +300,7 @@ export async function getServerSideProps(
|
||||
const { query, params, req, res } = ctx;
|
||||
const activeTab = query.tab;
|
||||
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -307,8 +316,8 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
@@ -317,7 +326,7 @@ export async function getServerSideProps(
|
||||
await helpers.redis.one.fetch({
|
||||
redisId: params?.redisId,
|
||||
});
|
||||
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
@@ -325,7 +334,7 @@ export async function getServerSideProps(
|
||||
activeTab: (activeTab || "general") as TabState,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
|
||||
@@ -2,11 +2,10 @@ import { ShowProjects } from "@/components/dashboard/projects/show";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import dynamic from "next/dynamic";
|
||||
import type React from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
@@ -38,7 +37,7 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const { user, session } = await validateRequest(req);
|
||||
|
||||
const helpers = createServerSideHelpers({
|
||||
router: appRouter,
|
||||
@@ -46,14 +45,14 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
await helpers.auth.get.prefetch();
|
||||
await helpers.user.get.prefetch();
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ShowRequests } from "@/components/dashboard/requests/show-requests";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import { IS_CLOUD } from "@dokploy/server/constants";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import type { ReactElement } from "react";
|
||||
import * as React from "react";
|
||||
|
||||
export default function Requests() {
|
||||
return <ShowRequests />;
|
||||
@@ -22,7 +22,7 @@ export async function getServerSideProps(
|
||||
},
|
||||
};
|
||||
}
|
||||
const { user } = await validateRequest(ctx.req, ctx.res);
|
||||
const { user } = await validateRequest(ctx.req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
|
||||
@@ -2,10 +2,11 @@ import { ShowBilling } from "@/components/dashboard/settings/billing/show-billin
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import { IS_CLOUD } from "@dokploy/server/constants";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
@@ -29,8 +30,8 @@ export async function getServerSideProps(
|
||||
};
|
||||
}
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
if (!user || user.rol === "user") {
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user || user.role === "member") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -45,13 +46,13 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
|
||||
await helpers.auth.get.prefetch();
|
||||
await helpers.user.get.prefetch();
|
||||
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { appRouter } from "@/server/api/root";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
const Page = () => {
|
||||
return (
|
||||
@@ -24,8 +24,8 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
if (!user || user.rol === "user") {
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user || user.role === "member") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -40,12 +40,12 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
await helpers.auth.get.prefetch();
|
||||
await helpers.user.get.prefetch();
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { appRouter } from "@/server/api/root";
|
||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
@@ -33,8 +33,8 @@ export async function getServerSideProps(
|
||||
},
|
||||
};
|
||||
}
|
||||
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||
if (!user || user.rol === "user") {
|
||||
const { user, session } = await validateRequest(ctx.req);
|
||||
if (!user || user.role === "member") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -48,12 +48,12 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
await helpers.auth.get.prefetch();
|
||||
await helpers.user.get.prefetch();
|
||||
|
||||
return {
|
||||
props: {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { appRouter } from "@/server/api/root";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
@@ -25,8 +25,8 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
if (!user || user.rol === "user") {
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user || user.role === "member") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -41,12 +41,12 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
await helpers.auth.get.prefetch();
|
||||
await helpers.user.get.prefetch();
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { appRouter } from "@/server/api/root";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
@@ -24,7 +24,7 @@ Page.getLayout = (page: ReactElement) => {
|
||||
export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||
const { user, session } = await validateRequest(ctx.req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -40,23 +40,21 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
await helpers.auth.get.prefetch();
|
||||
await helpers.user.get.prefetch();
|
||||
try {
|
||||
await helpers.project.all.prefetch();
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
const auth = await helpers.auth.get.fetch();
|
||||
|
||||
if (auth.rol === "user") {
|
||||
const user = await helpers.user.byAuthId.fetch({
|
||||
authId: auth.id,
|
||||
if (user.role === "member") {
|
||||
const userR = await helpers.user.one.fetch({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (!user.canAccessToGitProviders) {
|
||||
if (!userR?.canAccessToGitProviders) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -70,7 +68,7 @@ export async function getServerSideProps(
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
|
||||
219
apps/dokploy/pages/dashboard/settings/index.tsx
Normal file
219
apps/dokploy/pages/dashboard/settings/index.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { DialogFooter } from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
} from "@/components/ui/form";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import { Settings } from "lucide-react";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import { type ReactElement, useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import superjson from "superjson";
|
||||
import { z } from "zod";
|
||||
|
||||
const settings = z.object({
|
||||
cleanCacheOnApplications: z.boolean(),
|
||||
cleanCacheOnCompose: z.boolean(),
|
||||
cleanCacheOnPreviews: z.boolean(),
|
||||
});
|
||||
|
||||
type SettingsType = z.infer<typeof settings>;
|
||||
|
||||
const Page = () => {
|
||||
const { data, refetch } = api.user.get.useQuery();
|
||||
const { mutateAsync, isLoading, isError, error } =
|
||||
api.user.update.useMutation();
|
||||
const form = useForm<SettingsType>({
|
||||
defaultValues: {
|
||||
cleanCacheOnApplications: false,
|
||||
cleanCacheOnCompose: false,
|
||||
cleanCacheOnPreviews: false,
|
||||
},
|
||||
resolver: zodResolver(settings),
|
||||
});
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
cleanCacheOnApplications: data?.user.cleanupCacheApplications || false,
|
||||
cleanCacheOnCompose: data?.user.cleanupCacheOnCompose || false,
|
||||
cleanCacheOnPreviews: data?.user.cleanupCacheOnPreviews || false,
|
||||
});
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
|
||||
|
||||
const onSubmit = async (values: SettingsType) => {
|
||||
await mutateAsync({
|
||||
cleanupCacheApplications: values.cleanCacheOnApplications,
|
||||
cleanupCacheOnCompose: values.cleanCacheOnCompose,
|
||||
cleanupCacheOnPreviews: values.cleanCacheOnPreviews,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Settings updated");
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Something went wrong");
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto">
|
||||
<div className="rounded-xl bg-background shadow-md ">
|
||||
<CardHeader className="">
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
<Settings className="size-6 text-muted-foreground self-center" />
|
||||
Settings
|
||||
</CardTitle>
|
||||
<CardDescription>Manage your Dokploy settings</CardDescription>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 py-8 border-t">
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-add-security"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-2"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cleanCacheOnApplications"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Clean Cache on Applications</FormLabel>
|
||||
<FormDescription>
|
||||
Clean the cache after every application deployment
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cleanCacheOnPreviews"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Clean Cache on Previews</FormLabel>
|
||||
<FormDescription>
|
||||
Clean the cache after every preview deployment
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cleanCacheOnCompose"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Clean Cache on Compose</FormLabel>
|
||||
<FormDescription>
|
||||
Clean the cache after every compose deployment
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
form="hook-form-add-security"
|
||||
type="submit"
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
Page.getLayout = (page: ReactElement) => {
|
||||
return <DashboardLayout metaName="Server">{page}</DashboardLayout>;
|
||||
};
|
||||
export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(ctx.req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/",
|
||||
},
|
||||
};
|
||||
}
|
||||
if (user.role === "member") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/dashboard/settings/profile",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const helpers = createServerSideHelpers({
|
||||
router: appRouter,
|
||||
ctx: {
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
await helpers.user.get.prefetch();
|
||||
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { appRouter } from "@/server/api/root";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
@@ -25,8 +25,8 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
if (!user || user.rol === "user") {
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user || user.role === "member") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -41,12 +41,12 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
await helpers.auth.get.prefetch();
|
||||
await helpers.user.get.prefetch();
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { GenerateToken } from "@/components/dashboard/settings/profile/generate-token";
|
||||
import { ShowApiKeys } from "@/components/dashboard/settings/api/show-api-keys";
|
||||
import { ProfileForm } from "@/components/dashboard/settings/profile/profile-form";
|
||||
import { RemoveSelfAccount } from "@/components/dashboard/settings/profile/remove-self-account";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
|
||||
import { appRouter } from "@/server/api/root";
|
||||
@@ -9,28 +8,20 @@ import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
const { data } = api.auth.get.useQuery();
|
||||
const { data: user } = api.user.byAuthId.useQuery(
|
||||
{
|
||||
authId: data?.id || "",
|
||||
},
|
||||
{
|
||||
enabled: !!data?.id && data?.rol === "user",
|
||||
},
|
||||
);
|
||||
const { data } = api.user.get.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
// const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="h-full rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
|
||||
<ProfileForm />
|
||||
{(user?.canAccessToAPI || data?.rol === "admin") && <GenerateToken />}
|
||||
{(data?.canAccessToAPI || data?.role === "owner") && <ShowApiKeys />}
|
||||
|
||||
{isCloud && <RemoveSelfAccount />}
|
||||
{/* {isCloud && <RemoveSelfAccount />} */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -46,7 +37,7 @@ export async function getServerSideProps(
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const locale = getLocale(req.cookies);
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const { user, session } = await validateRequest(req);
|
||||
|
||||
const helpers = createServerSideHelpers({
|
||||
router: appRouter,
|
||||
@@ -54,19 +45,14 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
await helpers.auth.get.prefetch();
|
||||
if (user?.rol === "user") {
|
||||
await helpers.user.byAuthId.prefetch({
|
||||
authId: user.authId,
|
||||
});
|
||||
}
|
||||
await helpers.user.get.prefetch();
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { appRouter } from "@/server/api/root";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
@@ -25,8 +25,8 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
if (!user || user.rol === "user") {
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user || user.role === "member") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -40,12 +40,12 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
await helpers.auth.get.prefetch();
|
||||
await helpers.user.get.prefetch();
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { WebDomain } from "@/components/dashboard/settings/web-domain";
|
||||
import { WebServer } from "@/components/dashboard/settings/web-server";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
@@ -16,6 +15,49 @@ const Page = () => {
|
||||
<div className="h-full rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
|
||||
<WebDomain />
|
||||
<WebServer />
|
||||
{/* <Card className="h-full bg-sidebar p-2.5 rounded-xl ">
|
||||
<div className="rounded-xl bg-background shadow-md ">
|
||||
<CardHeader className="">
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
<LayoutDashboardIcon className="size-6 text-muted-foreground self-center" />
|
||||
Paid Features
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Enable or disable paid features like monitoring
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
Enable Paid Features:
|
||||
</span>
|
||||
|
||||
<Switch
|
||||
checked={data?.enablePaidFeatures}
|
||||
onCheckedChange={() => {
|
||||
update({
|
||||
enablePaidFeatures: !data?.enablePaidFeatures,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(
|
||||
`Paid features ${
|
||||
data?.enablePaidFeatures ? "disabled" : "enabled"
|
||||
} successfully`,
|
||||
);
|
||||
refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error updating paid features");
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
{data?.enablePaidFeatures && <SetupMonitoring />}
|
||||
</div>
|
||||
</Card> */}
|
||||
|
||||
{/* */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -39,7 +81,7 @@ export async function getServerSideProps(
|
||||
},
|
||||
};
|
||||
}
|
||||
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||
const { user, session } = await validateRequest(ctx.req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -48,7 +90,7 @@ export async function getServerSideProps(
|
||||
},
|
||||
};
|
||||
}
|
||||
if (user.rol === "user") {
|
||||
if (user.role === "member") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -63,12 +105,12 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
await helpers.auth.get.prefetch();
|
||||
await helpers.user.get.prefetch();
|
||||
|
||||
return {
|
||||
props: {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
@@ -27,7 +27,7 @@ export async function getServerSideProps(
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const locale = await getLocale(req.cookies);
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -36,7 +36,7 @@ export async function getServerSideProps(
|
||||
},
|
||||
};
|
||||
}
|
||||
if (user.rol === "user") {
|
||||
if (user.role === "member") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -51,12 +51,12 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
await helpers.auth.get.prefetch();
|
||||
await helpers.user.get.prefetch();
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { appRouter } from "@/server/api/root";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
@@ -24,7 +24,7 @@ Page.getLayout = (page: ReactElement) => {
|
||||
export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||
const { user, session } = await validateRequest(ctx.req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -33,30 +33,29 @@ export async function getServerSideProps(
|
||||
},
|
||||
};
|
||||
}
|
||||
const { req, res, resolvedUrl } = ctx;
|
||||
const { req, res } = ctx;
|
||||
const helpers = createServerSideHelpers({
|
||||
router: appRouter,
|
||||
ctx: {
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
|
||||
try {
|
||||
await helpers.project.all.prefetch();
|
||||
const auth = await helpers.auth.get.fetch();
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
|
||||
if (auth.rol === "user") {
|
||||
const user = await helpers.user.byAuthId.fetch({
|
||||
authId: auth.id,
|
||||
if (user.role === "member") {
|
||||
const userR = await helpers.user.one.fetch({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (!user.canAccessToSSHKeys) {
|
||||
if (!userR?.canAccessToSSHKeys) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -70,7 +69,7 @@ export async function getServerSideProps(
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ShowInvitations } from "@/components/dashboard/settings/users/show-invitations";
|
||||
import { ShowUsers } from "@/components/dashboard/settings/users/show-users";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
|
||||
@@ -5,13 +6,14 @@ import { appRouter } from "@/server/api/root";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<ShowUsers />
|
||||
<ShowInvitations />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -25,8 +27,9 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
if (!user || user.rol === "user") {
|
||||
const { user, session } = await validateRequest(req);
|
||||
|
||||
if (!user || user.role === "member") {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -41,12 +44,12 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
await helpers.auth.get.prefetch();
|
||||
await helpers.user.get.prefetch();
|
||||
await helpers.settings.isCloud.prefetch();
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import SwarmMonitorCard from "@/components/dashboard/swarm/monitoring-card";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import { IS_CLOUD } from "@dokploy/server/constants";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Dashboard = () => {
|
||||
return (
|
||||
<>
|
||||
<SwarmMonitorCard />
|
||||
</>
|
||||
);
|
||||
return <SwarmMonitorCard />;
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
@@ -31,7 +28,7 @@ export async function getServerSideProps(
|
||||
},
|
||||
};
|
||||
}
|
||||
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||
const { user, session } = await validateRequest(ctx.req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -48,21 +45,20 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
try {
|
||||
await helpers.project.all.prefetch();
|
||||
const auth = await helpers.auth.get.fetch();
|
||||
|
||||
if (auth.rol === "user") {
|
||||
const user = await helpers.user.byAuthId.fetch({
|
||||
authId: auth.id,
|
||||
if (user.role === "member") {
|
||||
const userR = await helpers.user.one.fetch({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (!user.canAccessToDocker) {
|
||||
if (!userR?.canAccessToDocker) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -76,7 +72,7 @@ export async function getServerSideProps(
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ShowTraefikSystem } from "@/components/dashboard/file-system/show-traefik-system";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import { IS_CLOUD } from "@dokploy/server/constants";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
|
||||
const Dashboard = () => {
|
||||
@@ -27,7 +28,7 @@ export async function getServerSideProps(
|
||||
},
|
||||
};
|
||||
}
|
||||
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||
const { user, session } = await validateRequest(ctx.req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -44,21 +45,20 @@ export async function getServerSideProps(
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
try {
|
||||
await helpers.project.all.prefetch();
|
||||
const auth = await helpers.auth.get.fetch();
|
||||
|
||||
if (auth.rol === "user") {
|
||||
const user = await helpers.user.byAuthId.fetch({
|
||||
authId: auth.id,
|
||||
if (user.role === "member") {
|
||||
const userR = await helpers.user.one.fetch({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (!user.canAccessToTraefikFiles) {
|
||||
if (!userR?.canAccessToTraefikFiles) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
@@ -72,7 +72,7 @@ export async function getServerSideProps(
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
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";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardContent, CardDescription } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -13,88 +19,186 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { IS_CLOUD, isAdminPresent, validateRequest } from "@dokploy/server";
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
InputOTPSlot,
|
||||
} from "@/components/ui/input-otp";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { IS_CLOUD, isAdminPresent } from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { REGEXP_ONLY_DIGITS } from "input-otp";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useEffect, useState } from "react";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const loginSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: "Email is required",
|
||||
})
|
||||
.email({
|
||||
message: "Email must be a valid email",
|
||||
}),
|
||||
|
||||
password: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: "Password is required",
|
||||
})
|
||||
.min(8, {
|
||||
message: "Password must be at least 8 characters",
|
||||
}),
|
||||
const LoginSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(8),
|
||||
});
|
||||
|
||||
type Login = z.infer<typeof loginSchema>;
|
||||
const _TwoFactorSchema = z.object({
|
||||
code: z.string().min(6),
|
||||
});
|
||||
|
||||
type AuthResponse = {
|
||||
is2FAEnabled: boolean;
|
||||
authId: string;
|
||||
};
|
||||
type LoginForm = z.infer<typeof LoginSchema>;
|
||||
|
||||
interface Props {
|
||||
IS_CLOUD: boolean;
|
||||
}
|
||||
export default function Home({ IS_CLOUD }: Props) {
|
||||
const [temp, setTemp] = useState<AuthResponse>({
|
||||
is2FAEnabled: false,
|
||||
authId: "",
|
||||
});
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
api.auth.login.useMutation();
|
||||
const router = useRouter();
|
||||
const form = useForm<Login>({
|
||||
const [isLoginLoading, setIsLoginLoading] = useState(false);
|
||||
const [isTwoFactorLoading, setIsTwoFactorLoading] = useState(false);
|
||||
const [isBackupCodeLoading, setIsBackupCodeLoading] = useState(false);
|
||||
const [isTwoFactor, setIsTwoFactor] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [twoFactorCode, setTwoFactorCode] = useState("");
|
||||
const [isBackupCodeModalOpen, setIsBackupCodeModalOpen] = useState(false);
|
||||
const [backupCode, setBackupCode] = useState("");
|
||||
const [isGithubLoading, setIsGithubLoading] = useState(false);
|
||||
const [isGoogleLoading, setIsGoogleLoading] = useState(false);
|
||||
const loginForm = useForm<LoginForm>({
|
||||
resolver: zodResolver(LoginSchema),
|
||||
defaultValues: {
|
||||
email: "",
|
||||
password: "",
|
||||
},
|
||||
resolver: zodResolver(loginSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
form.reset();
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
||||
|
||||
const onSubmit = async (values: Login) => {
|
||||
await mutateAsync({
|
||||
email: values.email.toLowerCase(),
|
||||
password: values.password,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.is2FAEnabled) {
|
||||
setTemp(data);
|
||||
} else {
|
||||
toast.success("Successfully signed in", {
|
||||
duration: 2000,
|
||||
});
|
||||
router.push("/dashboard/projects");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Signin failed", {
|
||||
duration: 2000,
|
||||
});
|
||||
const onSubmit = async (values: LoginForm) => {
|
||||
setIsLoginLoading(true);
|
||||
try {
|
||||
const { data, error } = await authClient.signIn.email({
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
setError(error.message || "An error occurred while logging in");
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (data?.twoFactorRedirect as boolean) {
|
||||
setTwoFactorCode("");
|
||||
setIsTwoFactor(true);
|
||||
toast.info("Please enter your 2FA code");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success("Logged in successfully");
|
||||
router.push("/dashboard/projects");
|
||||
} catch (_error) {
|
||||
toast.error("An error occurred while logging in");
|
||||
} finally {
|
||||
setIsLoginLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onTwoFactorSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (twoFactorCode.length !== 6) {
|
||||
toast.error("Please enter a valid 6-digit code");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsTwoFactorLoading(true);
|
||||
try {
|
||||
const { error } = await authClient.twoFactor.verifyTotp({
|
||||
code: twoFactorCode.replace(/\s/g, ""),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
setError(error.message || "An error occurred while verifying 2FA code");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success("Logged in successfully");
|
||||
router.push("/dashboard/projects");
|
||||
} catch (_error) {
|
||||
toast.error("An error occurred while verifying 2FA code");
|
||||
} finally {
|
||||
setIsTwoFactorLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onBackupCodeSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (backupCode.length < 8) {
|
||||
toast.error("Please enter a valid backup code");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsBackupCodeLoading(true);
|
||||
try {
|
||||
const { error } = await authClient.twoFactor.verifyBackupCode({
|
||||
code: backupCode.trim(),
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
setError(
|
||||
error.message || "An error occurred while verifying backup code",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success("Logged in successfully");
|
||||
router.push("/dashboard/projects");
|
||||
} catch (_error) {
|
||||
toast.error("An error occurred while verifying backup code");
|
||||
} finally {
|
||||
setIsBackupCodeLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGithubSignIn = async () => {
|
||||
setIsGithubLoading(true);
|
||||
try {
|
||||
const { error } = await authClient.signIn.social({
|
||||
provider: "github",
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("An error occurred while signing in with GitHub", {
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
} finally {
|
||||
setIsGithubLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoogleSignIn = async () => {
|
||||
setIsGoogleLoading(true);
|
||||
try {
|
||||
const { error } = await authClient.signIn.social({
|
||||
provider: "google",
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("An error occurred while signing in with Google", {
|
||||
description: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
} finally {
|
||||
setIsGoogleLoading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
@@ -109,31 +213,81 @@ export default function Home({ IS_CLOUD }: Props) {
|
||||
Enter your email and password to sign in
|
||||
</p>
|
||||
</div>
|
||||
{isError && (
|
||||
{error && (
|
||||
<AlertBlock type="error" className="my-2">
|
||||
<span>{error?.message}</span>
|
||||
<span>{error}</span>
|
||||
</AlertBlock>
|
||||
)}
|
||||
<CardContent className="p-0">
|
||||
{!temp.is2FAEnabled ? (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="grid gap-4">
|
||||
<div className="space-y-4">
|
||||
{!isTwoFactor ? (
|
||||
<>
|
||||
{IS_CLOUD && (
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
className="w-full mb-4"
|
||||
onClick={handleGithubSignIn}
|
||||
isLoading={isGithubLoading}
|
||||
>
|
||||
<svg viewBox="0 0 438.549 438.549" className="mr-2 size-4">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"
|
||||
/>
|
||||
</svg>
|
||||
Sign in with GitHub
|
||||
</Button>
|
||||
)}
|
||||
{IS_CLOUD && (
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
className="w-full mb-4"
|
||||
onClick={handleGoogleSignIn}
|
||||
isLoading={isGoogleLoading}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" className="mr-2 size-4">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
||||
/>
|
||||
</svg>
|
||||
Sign in with Google
|
||||
</Button>
|
||||
)}
|
||||
<Form {...loginForm}>
|
||||
<form
|
||||
onSubmit={loginForm.handleSubmit(onSubmit)}
|
||||
className="space-y-4"
|
||||
id="login-form"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
control={loginForm.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Email" {...field} />
|
||||
<Input placeholder="john@example.com" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
control={loginForm.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
@@ -141,7 +295,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
placeholder="Enter your password"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -149,15 +303,127 @@ export default function Home({ IS_CLOUD }: Props) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit" isLoading={isLoading} className="w-full">
|
||||
<Button
|
||||
className="w-full"
|
||||
type="submit"
|
||||
isLoading={isLoginLoading}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<form
|
||||
onSubmit={onTwoFactorSubmit}
|
||||
className="space-y-4"
|
||||
id="two-factor-form"
|
||||
autoComplete="off"
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>2FA Code</Label>
|
||||
<InputOTP
|
||||
value={twoFactorCode}
|
||||
onChange={setTwoFactorCode}
|
||||
maxLength={6}
|
||||
pattern={REGEXP_ONLY_DIGITS}
|
||||
autoComplete="off"
|
||||
>
|
||||
<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>
|
||||
<CardDescription>
|
||||
Enter the 6-digit code from your authenticator app
|
||||
</CardDescription>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsBackupCodeModalOpen(true)}
|
||||
className="text-sm text-muted-foreground hover:underline self-start mt-2"
|
||||
>
|
||||
Lost access to your authenticator app?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsTwoFactor(false);
|
||||
setTwoFactorCode("");
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full"
|
||||
type="submit"
|
||||
isLoading={isTwoFactorLoading}
|
||||
>
|
||||
Verify
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
) : (
|
||||
<Login2FA authId={temp.authId} />
|
||||
|
||||
<Dialog
|
||||
open={isBackupCodeModalOpen}
|
||||
onOpenChange={setIsBackupCodeModalOpen}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Enter Backup Code</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter one of your backup codes to access your account
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={onBackupCodeSubmit} className="space-y-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Backup Code</Label>
|
||||
<Input
|
||||
value={backupCode}
|
||||
onChange={(e) => setBackupCode(e.target.value)}
|
||||
placeholder="Enter your backup code"
|
||||
className="font-mono"
|
||||
/>
|
||||
<CardDescription>
|
||||
Enter one of the backup codes you received when setting up
|
||||
2FA
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsBackupCodeModalOpen(false);
|
||||
setBackupCode("");
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="w-full"
|
||||
type="submit"
|
||||
isLoading={isBackupCodeLoading}
|
||||
>
|
||||
Verify
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex flex-row justify-between flex-wrap">
|
||||
@@ -203,8 +469,7 @@ Home.getLayout = (page: ReactElement) => {
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
if (IS_CLOUD) {
|
||||
try {
|
||||
const { user } = await validateRequest(context.req, context.res);
|
||||
|
||||
const { user } = await validateRequest(context.req);
|
||||
if (user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -213,7 +478,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
},
|
||||
};
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (_error) {}
|
||||
|
||||
return {
|
||||
props: {
|
||||
@@ -232,7 +497,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
};
|
||||
}
|
||||
|
||||
const { user } = await validateRequest(context.req, context.res);
|
||||
const { user } = await validateRequest(context.req);
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { OnboardingLayout } from "@/components/layouts/onboarding-layout";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Logo } from "@/components/shared/logo";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { CardContent, CardDescription, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -16,10 +12,10 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { api } from "@/utils/api";
|
||||
import { IS_CLOUD, getUserByToken } from "@dokploy/server";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
@@ -30,6 +26,9 @@ import { z } from "zod";
|
||||
|
||||
const registerSchema = z
|
||||
.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
email: z
|
||||
.string()
|
||||
.min(1, {
|
||||
@@ -38,7 +37,6 @@ const registerSchema = z
|
||||
.email({
|
||||
message: "Email must be a valid email",
|
||||
}),
|
||||
|
||||
password: z
|
||||
.string()
|
||||
.min(1, {
|
||||
@@ -71,11 +69,17 @@ interface Props {
|
||||
token: string;
|
||||
invitation: Awaited<ReturnType<typeof getUserByToken>>;
|
||||
isCloud: boolean;
|
||||
userAlreadyExists: boolean;
|
||||
}
|
||||
|
||||
const Invitation = ({ token, invitation, isCloud }: Props) => {
|
||||
const Invitation = ({
|
||||
token,
|
||||
invitation,
|
||||
isCloud,
|
||||
userAlreadyExists,
|
||||
}: Props) => {
|
||||
const router = useRouter();
|
||||
const { data } = api.admin.getUserByToken.useQuery(
|
||||
const { data } = api.user.getUserByToken.useQuery(
|
||||
{
|
||||
token,
|
||||
},
|
||||
@@ -85,11 +89,9 @@ const Invitation = ({ token, invitation, isCloud }: Props) => {
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync, error, isError, isSuccess } =
|
||||
api.auth.createUser.useMutation();
|
||||
|
||||
const form = useForm<Register>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
email: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
@@ -98,9 +100,9 @@ const Invitation = ({ token, invitation, isCloud }: Props) => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.auth?.email) {
|
||||
if (data?.email) {
|
||||
form.reset({
|
||||
email: data?.auth?.email || "",
|
||||
email: data?.email || "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
});
|
||||
@@ -108,20 +110,32 @@ const Invitation = ({ token, invitation, isCloud }: Props) => {
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
|
||||
|
||||
const onSubmit = async (values: Register) => {
|
||||
await mutateAsync({
|
||||
id: data?.authId,
|
||||
password: values.password,
|
||||
token: token,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("User registered successfuly", {
|
||||
description:
|
||||
"Please check your inbox or spam folder to confirm your account.",
|
||||
duration: 100000,
|
||||
});
|
||||
router.push("/dashboard/projects");
|
||||
})
|
||||
.catch((e) => e);
|
||||
try {
|
||||
const { error } = await authClient.signUp.email({
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
name: values.name,
|
||||
fetchOptions: {
|
||||
headers: {
|
||||
"x-dokploy-token": token,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const _result = await authClient.organization.acceptInvitation({
|
||||
invitationId: token,
|
||||
});
|
||||
|
||||
toast.success("Account created successfully");
|
||||
router.push("/dashboard/projects");
|
||||
} catch (_error) {
|
||||
toast.error("An error occurred while creating your account");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -138,114 +152,155 @@ const Invitation = ({ token, invitation, isCloud }: Props) => {
|
||||
</Link>
|
||||
Invitation
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Fill the form below to create your account
|
||||
</CardDescription>
|
||||
<div className="w-full">
|
||||
<div className="p-3" />
|
||||
{userAlreadyExists ? (
|
||||
<div className="flex flex-col gap-4 justify-center items-center">
|
||||
<AlertBlock type="success">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="font-medium">Valid Invitation!</span>
|
||||
<span className="text-sm text-green-600 dark:text-green-400">
|
||||
We detected that you already have an account with this
|
||||
email. Please sign in to accept the invitation.
|
||||
</span>
|
||||
</div>
|
||||
</AlertBlock>
|
||||
|
||||
{isError && (
|
||||
<div className="mx-5 my-2 flex flex-row items-center gap-2 rounded-lg 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>
|
||||
)}
|
||||
<Button asChild variant="default" className="w-full">
|
||||
<Link href="/">Sign In</Link>
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<CardDescription>
|
||||
Fill the form below to create your account
|
||||
</CardDescription>
|
||||
<div className="w-full">
|
||||
<div className="p-3" />
|
||||
|
||||
<CardContent className="p-0">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid gap-4"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input disabled placeholder="Email" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* {isError && (
|
||||
<div className="mx-5 my-2 flex flex-row items-center gap-2 rounded-lg 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>
|
||||
)} */}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="confirmPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Confirm Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Confirm Password"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={form.formState.isSubmitting}
|
||||
className="w-full"
|
||||
<CardContent className="p-0">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid gap-4"
|
||||
>
|
||||
Register
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Enter your name"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
disabled
|
||||
placeholder="Email"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="mt-4 text-sm flex flex-row justify-between gap-2 w-full">
|
||||
{isCloud && (
|
||||
<>
|
||||
<Link
|
||||
className="hover:underline text-muted-foreground"
|
||||
href="/"
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="confirmPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Confirm Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Confirm Password"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={form.formState.isSubmitting}
|
||||
className="w-full"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
<Link
|
||||
className="hover:underline text-muted-foreground"
|
||||
href="/send-reset-password"
|
||||
>
|
||||
Lost your password?
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</div>
|
||||
Register
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-sm flex flex-row justify-between gap-2 w-full">
|
||||
{isCloud && (
|
||||
<>
|
||||
<Link
|
||||
className="hover:underline text-muted-foreground"
|
||||
href="/"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
<Link
|
||||
className="hover:underline text-muted-foreground"
|
||||
href="/send-reset-password"
|
||||
>
|
||||
Lost your password?
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// http://localhost:3000/invitation?token=CZK4BLrUdMa32RVkAdZiLsPDdvnPiAgZ
|
||||
// /f7af93acc1a99eae864972ab4c92fee089f0d83473d415ede8e821e5dbabe79c
|
||||
export default Invitation;
|
||||
Invitation.getLayout = (page: ReactElement) => {
|
||||
return <OnboardingLayout>{page}</OnboardingLayout>;
|
||||
@@ -255,6 +310,15 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||
|
||||
const token = query.token;
|
||||
|
||||
// if (IS_CLOUD) {
|
||||
// return {
|
||||
// redirect: {
|
||||
// permanent: true,
|
||||
// destination: "/",
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
|
||||
if (typeof token !== "string") {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -267,6 +331,17 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||
try {
|
||||
const invitation = await getUserByToken(token);
|
||||
|
||||
if (invitation.userAlreadyExists) {
|
||||
return {
|
||||
props: {
|
||||
isCloud: IS_CLOUD,
|
||||
token: token,
|
||||
invitation: invitation,
|
||||
userAlreadyExists: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (invitation.isExpired) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -284,6 +359,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
|
||||
@@ -2,12 +2,7 @@ import { OnboardingLayout } from "@/components/layouts/onboarding-layout";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Logo } from "@/components/shared/logo";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { CardContent, CardDescription, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -17,20 +12,23 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { IS_CLOUD, isAdminPresent, validateRequest } from "@dokploy/server";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useEffect } from "react";
|
||||
import { type ReactElement, useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const registerSchema = z
|
||||
.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
email: z
|
||||
.string()
|
||||
.min(1, {
|
||||
@@ -74,11 +72,13 @@ interface Props {
|
||||
|
||||
const Register = ({ isCloud }: Props) => {
|
||||
const router = useRouter();
|
||||
const { mutateAsync, error, isError, data } =
|
||||
api.auth.createAdmin.useMutation();
|
||||
const [isError, setIsError] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [data, setData] = useState<any>(null);
|
||||
|
||||
const form = useForm<Register>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
email: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
@@ -91,19 +91,25 @@ const Register = ({ isCloud }: Props) => {
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
||||
|
||||
const onSubmit = async (values: Register) => {
|
||||
await mutateAsync({
|
||||
email: values.email.toLowerCase(),
|
||||
const { data, error } = await authClient.signUp.email({
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("User registered successfuly", {
|
||||
duration: 2000,
|
||||
});
|
||||
if (!isCloud) {
|
||||
router.push("/");
|
||||
}
|
||||
})
|
||||
.catch((e) => e);
|
||||
name: values.name,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
setIsError(true);
|
||||
setError(error.message || "An error occurred");
|
||||
} else {
|
||||
toast.success("User registered successfuly", {
|
||||
duration: 2000,
|
||||
});
|
||||
if (!isCloud) {
|
||||
router.push("/");
|
||||
} else {
|
||||
setData(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="">
|
||||
@@ -125,15 +131,15 @@ const Register = ({ isCloud }: Props) => {
|
||||
</CardDescription>
|
||||
<div className="mx-auto w-full max-w-lg bg-transparent">
|
||||
{isError && (
|
||||
<div className="mx-5 my-2 flex flex-row items-center gap-2 rounded-lg bg-red-50 p-2 dark:bg-red-950">
|
||||
<div className=" my-2 flex flex-row items-center gap-2 rounded-lg 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}
|
||||
{error}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{data?.type === "cloud" && (
|
||||
<AlertBlock type="success" className="mx-4 my-2">
|
||||
{isCloud && data && (
|
||||
<AlertBlock type="success" className="my-2">
|
||||
<span>
|
||||
Registered successfully, please check your inbox or spam
|
||||
folder to confirm your account.
|
||||
@@ -147,6 +153,19 @@ const Register = ({ isCloud }: Props) => {
|
||||
className="grid gap-4"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="name" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
@@ -242,7 +261,7 @@ Register.getLayout = (page: ReactElement) => {
|
||||
};
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
if (IS_CLOUD) {
|
||||
const { user } = await validateRequest(context.req, context.res);
|
||||
const { user } = await validateRequest(context.req);
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
|
||||
@@ -12,17 +12,13 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { db } from "@/server/db";
|
||||
import { auth } from "@/server/db/schema";
|
||||
import { api } from "@/utils/api";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { IS_CLOUD } from "@dokploy/server";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { isBefore } from "date-fns";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useEffect } from "react";
|
||||
import { type ReactElement, useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
@@ -54,11 +50,12 @@ const loginSchema = z
|
||||
type Login = z.infer<typeof loginSchema>;
|
||||
|
||||
interface Props {
|
||||
token: string;
|
||||
tokenResetPassword: string;
|
||||
}
|
||||
export default function Home({ token }: Props) {
|
||||
const { mutateAsync, isLoading, isError, error } =
|
||||
api.auth.resetPassword.useMutation();
|
||||
export default function Home({ tokenResetPassword }: Props) {
|
||||
const [token, setToken] = useState<string | null>(tokenResetPassword);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
const form = useForm<Login>({
|
||||
defaultValues: {
|
||||
@@ -68,26 +65,32 @@ export default function Home({ token }: Props) {
|
||||
resolver: zodResolver(loginSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const token = new URLSearchParams(window.location.search).get("token");
|
||||
|
||||
if (token) {
|
||||
setToken(token);
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
useEffect(() => {
|
||||
form.reset();
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
||||
|
||||
const onSubmit = async (values: Login) => {
|
||||
await mutateAsync({
|
||||
resetPasswordToken: token,
|
||||
password: values.password,
|
||||
})
|
||||
.then((data) => {
|
||||
toast.success("Password reset successfully", {
|
||||
duration: 2000,
|
||||
});
|
||||
router.push("/");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error resetting password", {
|
||||
duration: 2000,
|
||||
});
|
||||
});
|
||||
setIsLoading(true);
|
||||
const { error } = await authClient.resetPassword({
|
||||
newPassword: values.password,
|
||||
token: token || "",
|
||||
});
|
||||
|
||||
if (error) {
|
||||
setError(error.message || "An error occurred");
|
||||
} else {
|
||||
toast.success("Password reset successfully");
|
||||
router.push("/");
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center ">
|
||||
@@ -104,9 +107,9 @@ export default function Home({ token }: Props) {
|
||||
|
||||
<div className="w-full">
|
||||
<CardContent className="p-0">
|
||||
{isError && (
|
||||
{error && (
|
||||
<AlertBlock type="error" className="my-2">
|
||||
{error?.message}
|
||||
{error}
|
||||
</AlertBlock>
|
||||
)}
|
||||
<Form {...form}>
|
||||
@@ -194,35 +197,9 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
};
|
||||
}
|
||||
|
||||
const authR = await db.query.auth.findFirst({
|
||||
where: eq(auth.resetPasswordToken, token),
|
||||
});
|
||||
|
||||
if (!authR || authR?.resetPasswordExpiresAt === null) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/",
|
||||
},
|
||||
};
|
||||
}
|
||||
const isExpired = isBefore(
|
||||
new Date(authR.resetPasswordExpiresAt),
|
||||
new Date(),
|
||||
);
|
||||
|
||||
if (isExpired) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
token: authR.resetPasswordToken,
|
||||
tokenResetPassword: token,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
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";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { CardContent, CardDescription, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -18,7 +12,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { IS_CLOUD } from "@dokploy/server";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
@@ -48,13 +42,14 @@ type AuthResponse = {
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
const [temp, setTemp] = useState<AuthResponse>({
|
||||
const [temp, _setTemp] = useState<AuthResponse>({
|
||||
is2FAEnabled: false,
|
||||
authId: "",
|
||||
});
|
||||
const { mutateAsync, isLoading, isError, error } =
|
||||
api.auth.sendResetPasswordEmail.useMutation();
|
||||
const router = useRouter();
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const _router = useRouter();
|
||||
const form = useForm<Login>({
|
||||
defaultValues: {
|
||||
email: "",
|
||||
@@ -67,19 +62,20 @@ export default function Home() {
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
||||
|
||||
const onSubmit = async (values: Login) => {
|
||||
await mutateAsync({
|
||||
setIsLoading(true);
|
||||
const { error } = await authClient.forgetPassword({
|
||||
email: values.email,
|
||||
})
|
||||
.then((data) => {
|
||||
toast.success("Email sent", {
|
||||
duration: 2000,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error sending email", {
|
||||
duration: 2000,
|
||||
});
|
||||
redirectTo: "/reset-password",
|
||||
});
|
||||
if (error) {
|
||||
setError(error.message || "An error occurred");
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
toast.success("Email sent", {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
return (
|
||||
<div className="flex w-full items-center justify-center ">
|
||||
@@ -95,9 +91,9 @@ export default function Home() {
|
||||
|
||||
<div className="mx-auto w-full max-w-lg bg-transparent ">
|
||||
<CardContent className="p-0">
|
||||
{isError && (
|
||||
{error && (
|
||||
<AlertBlock type="error" className="my-2">
|
||||
{error?.message}
|
||||
{error}
|
||||
</AlertBlock>
|
||||
)}
|
||||
{!temp.is2FAEnabled ? (
|
||||
@@ -131,9 +127,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">
|
||||
@@ -155,7 +149,7 @@ export default function Home() {
|
||||
Home.getLayout = (page: ReactElement) => {
|
||||
return <OnboardingLayout>{page}</OnboardingLayout>;
|
||||
};
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
export async function getServerSideProps(_context: GetServerSidePropsContext) {
|
||||
if (!IS_CLOUD) {
|
||||
return {
|
||||
redirect: {
|
||||
|
||||
@@ -30,7 +30,41 @@ const Home: NextPage = () => {
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-white">
|
||||
<SwaggerUI spec={spec} />
|
||||
<SwaggerUI
|
||||
spec={spec}
|
||||
persistAuthorization={true}
|
||||
plugins={[
|
||||
{
|
||||
statePlugins: {
|
||||
auth: {
|
||||
wrapActions: {
|
||||
authorize: (ori: any) => (args: any) => {
|
||||
const result = ori(args);
|
||||
const apiKey = args?.apiKey?.value;
|
||||
if (apiKey) {
|
||||
localStorage.setItem("swagger_api_key", apiKey);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
logout: (ori: any) => (args: any) => {
|
||||
const result = ori(args);
|
||||
localStorage.removeItem("swagger_api_key");
|
||||
return result;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]}
|
||||
requestInterceptor={(request: any) => {
|
||||
const apiKey = localStorage.getItem("swagger_api_key");
|
||||
if (apiKey) {
|
||||
request.headers = request.headers || {};
|
||||
request.headers["x-api-key"] = apiKey;
|
||||
}
|
||||
return request;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -38,7 +72,7 @@ const Home: NextPage = () => {
|
||||
export default Home;
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const { req, res } = context;
|
||||
const { user, session } = await validateRequest(context.req, context.res);
|
||||
const { user, session } = await validateRequest(context.req);
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -53,17 +87,17 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
req: req as any,
|
||||
res: res as any,
|
||||
db: null as any,
|
||||
session: session,
|
||||
user: user,
|
||||
session: session as any,
|
||||
user: user as any,
|
||||
},
|
||||
transformer: superjson,
|
||||
});
|
||||
if (user.rol === "user") {
|
||||
const result = await helpers.user.byAuthId.fetch({
|
||||
authId: user.id,
|
||||
if (user.role === "member") {
|
||||
const userR = await helpers.user.one.fetch({
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (!result.canAccessToAPI) {
|
||||
if (!userR?.canAccessToAPI) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
|
||||
Reference in New Issue
Block a user