mirror of
https://github.com/andwati/dokploy.git
synced 2026-10-18 11:12:56 +00:00
Merge remote-tracking branch 'upstream/canary' into feat/domain-enable-disable
# Conflicts: # apps/dokploy/drizzle/meta/0182_snapshot.json # apps/dokploy/drizzle/meta/_journal.json
This commit is contained in:
@@ -112,7 +112,7 @@
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tsc-alias": "1.8.10",
|
||||
"tsx": "^4.22.4",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^7.0.2"
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"engines": {
|
||||
|
||||
@@ -66,6 +66,7 @@ export const organization = pgTable("organization", {
|
||||
logo: text("logo"),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
metadata: text("metadata"),
|
||||
defaultRole: text("default_role"),
|
||||
ownerId: text("owner_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import {
|
||||
Body,
|
||||
Container,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Img,
|
||||
Preview,
|
||||
Section,
|
||||
Tailwind,
|
||||
Text,
|
||||
} from "@react-email/components";
|
||||
import { emailTailwindConfig } from "../tailwind-config";
|
||||
|
||||
export type TemplateProps = {
|
||||
serverName: string;
|
||||
type: "CPU" | "Memory";
|
||||
value: string;
|
||||
threshold: string;
|
||||
message: string;
|
||||
date: string;
|
||||
};
|
||||
|
||||
export const ServerThresholdEmail = ({
|
||||
serverName = "my-server",
|
||||
type = "CPU",
|
||||
value = "95.00",
|
||||
threshold = "90.00",
|
||||
message = "Resource usage exceeded the configured threshold",
|
||||
date = "2023-05-01T00:00:00.000Z",
|
||||
}: TemplateProps) => {
|
||||
const previewText = `Server ${type} alert for ${serverName} ⚠️`;
|
||||
return (
|
||||
<Html>
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind config={emailTailwindConfig}>
|
||||
<Head />
|
||||
|
||||
<Body className="bg-white my-auto mx-auto font-sans px-2">
|
||||
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
|
||||
<Section className="mt-[32px]">
|
||||
<Img
|
||||
src={
|
||||
"https://raw.githubusercontent.com/Dokploy/dokploy/refs/heads/canary/apps/dokploy/logo.png"
|
||||
}
|
||||
width="100"
|
||||
height="50"
|
||||
alt="Dokploy"
|
||||
className="my-0 mx-auto"
|
||||
/>
|
||||
</Section>
|
||||
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
|
||||
⚠️ Server {type} alert for <strong>{serverName}</strong>
|
||||
</Heading>
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
Hello,
|
||||
</Text>
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
The {type} usage on <strong>{serverName}</strong> exceeded the
|
||||
configured threshold.
|
||||
</Text>
|
||||
<Section className="flex text-black text-[14px] leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
|
||||
<Text className="!leading-3 font-bold">Details: </Text>
|
||||
<Text className="!leading-3">
|
||||
Server Name: <strong>{serverName}</strong>
|
||||
</Text>
|
||||
<Text className="!leading-3">
|
||||
Type: <strong>{type}</strong>
|
||||
</Text>
|
||||
<Text className="!leading-3">
|
||||
Current Value: <strong>{value}%</strong>
|
||||
</Text>
|
||||
<Text className="!leading-3">
|
||||
Threshold: <strong>{threshold}%</strong>
|
||||
</Text>
|
||||
<Text className="!leading-3">
|
||||
Date: <strong>{date}</strong>
|
||||
</Text>
|
||||
</Section>
|
||||
<Section className="flex text-black text-[14px] mt-4 leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
|
||||
<Text className="!leading-3 font-bold">Message: </Text>
|
||||
<Text className="text-[12px] leading-[24px]">{message}</Text>
|
||||
</Section>
|
||||
</Container>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServerThresholdEmail;
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
getUserByToken,
|
||||
} from "../services/admin";
|
||||
import { createAuditLog } from "../services/proprietary/audit-log";
|
||||
import { resolveOrganizationDefaultRole } from "../services/proprietary/license-key";
|
||||
import {
|
||||
getWebServerSettings,
|
||||
updateWebServerSettings,
|
||||
@@ -289,10 +290,13 @@ const createBetterAuth = () =>
|
||||
message: "Provider not found",
|
||||
});
|
||||
}
|
||||
const defaultRole = provider.organizationId
|
||||
? await resolveOrganizationDefaultRole(provider.organizationId)
|
||||
: "member";
|
||||
await db.insert(schema.member).values({
|
||||
userId: user.id,
|
||||
organizationId: provider?.organizationId || "",
|
||||
role: "member",
|
||||
role: defaultRole,
|
||||
createdAt: new Date(),
|
||||
isDefault: true,
|
||||
});
|
||||
|
||||
@@ -802,7 +802,7 @@ export const findAllDeploymentsByComposeId = async (composeId: string) => {
|
||||
|
||||
const centralizedDeploymentsWith = {
|
||||
application: {
|
||||
columns: { applicationId: true, name: true, appName: true },
|
||||
columns: { applicationId: true, name: true, appName: true, icon: true },
|
||||
with: {
|
||||
environment: {
|
||||
columns: { environmentId: true, name: true },
|
||||
@@ -821,7 +821,7 @@ const centralizedDeploymentsWith = {
|
||||
},
|
||||
},
|
||||
compose: {
|
||||
columns: { composeId: true, name: true, appName: true },
|
||||
columns: { composeId: true, name: true, appName: true, icon: true },
|
||||
with: {
|
||||
environment: {
|
||||
columns: { environmentId: true, name: true },
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { user } from "@dokploy/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import {
|
||||
organization,
|
||||
organizationRole,
|
||||
user,
|
||||
} from "@dokploy/server/db/schema";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { getOrganizationOwnerId } from "./sso";
|
||||
|
||||
export const hasValidLicense = async (organizationId: string) => {
|
||||
@@ -22,3 +26,35 @@ export const hasValidLicense = async (organizationId: string) => {
|
||||
currentUser?.isValidEnterpriseLicense
|
||||
);
|
||||
};
|
||||
|
||||
export const resolveOrganizationDefaultRole = async (
|
||||
organizationId: string,
|
||||
) => {
|
||||
const org = await db.query.organization.findFirst({
|
||||
where: eq(organization.id, organizationId),
|
||||
columns: { defaultRole: true },
|
||||
});
|
||||
const defaultRole = org?.defaultRole;
|
||||
|
||||
if (!defaultRole || defaultRole === "owner") {
|
||||
return "member";
|
||||
}
|
||||
|
||||
if (defaultRole === "admin" || defaultRole === "member") {
|
||||
return defaultRole;
|
||||
}
|
||||
|
||||
const customRole = await db.query.organizationRole.findFirst({
|
||||
where: and(
|
||||
eq(organizationRole.organizationId, organizationId),
|
||||
eq(organizationRole.role, defaultRole),
|
||||
),
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
if (!customRole || !(await hasValidLicense(organizationId))) {
|
||||
return "member";
|
||||
}
|
||||
|
||||
return defaultRole;
|
||||
};
|
||||
|
||||
@@ -136,7 +136,7 @@ export function processValue(
|
||||
if (params.length === 1 && params[0] && params[0].match(/^\d{1,3}$/)) {
|
||||
return generateJwt({ length: Number.parseInt(params[0], 10) });
|
||||
}
|
||||
let [secret, payload] = params;
|
||||
let [secret, payload] = params as [string?, string?];
|
||||
if (typeof payload === "string" && variables[payload]) {
|
||||
payload = variables[payload];
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { network, patch } from "@dokploy/server/db/schema";
|
||||
import type { Compose } from "@dokploy/server/services/compose";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import { quote } from "shell-quote";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
import { cloneBitbucketRepository } from "../providers/bitbucket";
|
||||
@@ -128,8 +129,11 @@ exit 1;
|
||||
const encodedContent = encodeBase64(composeString);
|
||||
return `echo "${encodedContent}" | base64 -d > "${path}";`;
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
return `echo "❌ Has occurred an error: ${error?.message || error}";
|
||||
const message =
|
||||
error instanceof Error ? error.message : String(error ?? "");
|
||||
// The error message embeds user-controlled fields (e.g. serviceName) and is
|
||||
// executed as part of the compose build shell script, so it must be escaped.
|
||||
return `echo ${quote([`❌ Has occurred an error: ${message}`])};
|
||||
exit 1;
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { render } from "@react-email/components";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { db } from "../../db";
|
||||
import { notifications } from "../../db/schema";
|
||||
import ServerThresholdEmail from "../../emails/emails/server-threshold";
|
||||
import {
|
||||
sendCustomNotification,
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
sendLarkNotification,
|
||||
sendMattermostNotification,
|
||||
sendNtfyNotification,
|
||||
sendPushoverNotification,
|
||||
sendResendNotification,
|
||||
sendSlackNotification,
|
||||
sendTeamsNotification,
|
||||
sendTelegramNotification,
|
||||
@@ -39,6 +45,9 @@ export const sendServerThresholdNotifications = async (
|
||||
discord: true,
|
||||
telegram: true,
|
||||
slack: true,
|
||||
resend: true,
|
||||
gotify: true,
|
||||
ntfy: true,
|
||||
mattermost: true,
|
||||
custom: true,
|
||||
lark: true,
|
||||
@@ -52,9 +61,13 @@ export const sendServerThresholdNotifications = async (
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const {
|
||||
email,
|
||||
discord,
|
||||
telegram,
|
||||
slack,
|
||||
resend,
|
||||
gotify,
|
||||
ntfy,
|
||||
mattermost,
|
||||
custom,
|
||||
lark,
|
||||
@@ -63,6 +76,64 @@ export const sendServerThresholdNotifications = async (
|
||||
} = notification;
|
||||
|
||||
try {
|
||||
if (email || resend) {
|
||||
const template = await render(
|
||||
ServerThresholdEmail({
|
||||
serverName: payload.ServerName,
|
||||
type: payload.Type,
|
||||
value: payload.Value.toFixed(2),
|
||||
threshold: payload.Threshold.toFixed(2),
|
||||
message: payload.Message,
|
||||
date: date.toLocaleString(),
|
||||
}),
|
||||
);
|
||||
|
||||
if (email) {
|
||||
await sendEmailNotification(
|
||||
email,
|
||||
`Server ${payload.Type} alert for ${payload.ServerName}`,
|
||||
template,
|
||||
);
|
||||
}
|
||||
|
||||
if (resend) {
|
||||
await sendResendNotification(
|
||||
resend,
|
||||
`Server ${payload.Type} alert for ${payload.ServerName}`,
|
||||
template,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (gotify) {
|
||||
const decorate = (decoration: string, text: string) =>
|
||||
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
||||
|
||||
await sendGotifyNotification(
|
||||
gotify,
|
||||
decorate("⚠️", `Server ${payload.Type} Alert`),
|
||||
`${decorate("🏷️", `Server: ${payload.ServerName}`)}` +
|
||||
`${decorate("📊", `Current Value: ${payload.Value.toFixed(2)}%`)}` +
|
||||
`${decorate("⚠️", `Threshold: ${payload.Threshold.toFixed(2)}%`)}` +
|
||||
`${decorate("📜", `Message: ${payload.Message}`)}` +
|
||||
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (ntfy) {
|
||||
await sendNtfyNotification(
|
||||
ntfy,
|
||||
`Server ${payload.Type} Alert`,
|
||||
"warning",
|
||||
"",
|
||||
`🏷️Server: ${payload.ServerName}\n` +
|
||||
`📊Current Value: ${payload.Value.toFixed(2)}%\n` +
|
||||
`⚠️Threshold: ${payload.Threshold.toFixed(2)}%\n` +
|
||||
`📜Message: ${payload.Message}\n` +
|
||||
`🕒Date: ${date.toLocaleString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (discord) {
|
||||
const decorate = (decoration: string, text: string) =>
|
||||
`${discord.decoration ? decoration : ""} ${text}`.trim();
|
||||
|
||||
@@ -79,6 +79,10 @@ const generateRestoreCommand = (
|
||||
}
|
||||
};
|
||||
|
||||
// Dumps taken with `--databases` carry `USE`/`CREATE DATABASE` statements that
|
||||
// would redirect the restore away from the database selected in the dialog.
|
||||
export const stripDatabaseSwitchCommand = `grep -viE '^[[:space:]]*(use|create[[:space:]]+database)[[:space:]]'`;
|
||||
|
||||
const getMongoSpecificCommand = (
|
||||
rcloneCommand: string,
|
||||
restoreCommand: string,
|
||||
@@ -125,7 +129,9 @@ export const getRestoreCommand = ({
|
||||
const restoreCommand = generateRestoreCommand(type, credentials);
|
||||
let cmd = `CONTAINER_ID=$(${containerSearch})`;
|
||||
|
||||
if (type !== "mongo") {
|
||||
if (type === "mysql" || type === "mariadb") {
|
||||
cmd += ` && ${rcloneCommand} | ${stripDatabaseSwitchCommand} | ${restoreCommand}`;
|
||||
} else if (type !== "mongo") {
|
||||
cmd += ` && ${rcloneCommand} | ${restoreCommand}`;
|
||||
} else {
|
||||
cmd += ` && ${getMongoSpecificCommand(rcloneCommand, restoreCommand, backupFile || "")}`;
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"plugins": [{ "name": "next" }],
|
||||
"outDir": "./dist",
|
||||
/* Path Aliases */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@dokploy/server/*": ["./src/*"]
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
"isolatedModules": false,
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
"moduleResolution": "Node",
|
||||
"moduleResolution": "bundler",
|
||||
"rootDir": "./src",
|
||||
"baseUrl": ".",
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@dokploy/server/*": ["./src/*"]
|
||||
|
||||
Reference in New Issue
Block a user