mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge remote-tracking branch 'dokploy/canary' into check-for-updates
This commit is contained in:
@@ -6,13 +6,144 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { ShieldCheck } from "lucide-react";
|
import { AlertCircle, Link, ShieldCheck } from "lucide-react";
|
||||||
import { AddCertificate } from "./add-certificate";
|
import { AddCertificate } from "./add-certificate";
|
||||||
import { DeleteCertificate } from "./delete-certificate";
|
import { DeleteCertificate } from "./delete-certificate";
|
||||||
|
|
||||||
export const ShowCertificates = () => {
|
export const ShowCertificates = () => {
|
||||||
const { data } = api.certificates.all.useQuery();
|
const { data } = api.certificates.all.useQuery();
|
||||||
|
|
||||||
|
const extractExpirationDate = (certData: string): Date | null => {
|
||||||
|
try {
|
||||||
|
const match = certData.match(
|
||||||
|
/-----BEGIN CERTIFICATE-----\s*([^-]+)\s*-----END CERTIFICATE-----/,
|
||||||
|
);
|
||||||
|
if (!match?.[1]) return null;
|
||||||
|
|
||||||
|
const base64Cert = match[1].replace(/\s/g, "");
|
||||||
|
const binaryStr = window.atob(base64Cert);
|
||||||
|
const bytes = new Uint8Array(binaryStr.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < binaryStr.length; i++) {
|
||||||
|
bytes[i] = binaryStr.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dateFound = 0;
|
||||||
|
for (let i = 0; i < bytes.length - 2; i++) {
|
||||||
|
if (bytes[i] === 0x17 || bytes[i] === 0x18) {
|
||||||
|
const dateType = bytes[i];
|
||||||
|
const dateLength = bytes[i + 1];
|
||||||
|
if (typeof dateLength === "undefined") continue;
|
||||||
|
|
||||||
|
if (dateFound === 0) {
|
||||||
|
dateFound++;
|
||||||
|
i += dateLength + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dateStr = "";
|
||||||
|
for (let j = 0; j < dateLength; j++) {
|
||||||
|
const charCode = bytes[i + 2 + j];
|
||||||
|
if (typeof charCode === "undefined") continue;
|
||||||
|
dateStr += String.fromCharCode(charCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dateType === 0x17) {
|
||||||
|
// UTCTime (YYMMDDhhmmssZ)
|
||||||
|
const year = Number.parseInt(dateStr.slice(0, 2));
|
||||||
|
const fullYear = year >= 50 ? 1900 + year : 2000 + year;
|
||||||
|
return new Date(
|
||||||
|
Date.UTC(
|
||||||
|
fullYear,
|
||||||
|
Number.parseInt(dateStr.slice(2, 4)) - 1,
|
||||||
|
Number.parseInt(dateStr.slice(4, 6)),
|
||||||
|
Number.parseInt(dateStr.slice(6, 8)),
|
||||||
|
Number.parseInt(dateStr.slice(8, 10)),
|
||||||
|
Number.parseInt(dateStr.slice(10, 12)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneralizedTime (YYYYMMDDhhmmssZ)
|
||||||
|
return new Date(
|
||||||
|
Date.UTC(
|
||||||
|
Number.parseInt(dateStr.slice(0, 4)),
|
||||||
|
Number.parseInt(dateStr.slice(4, 6)) - 1,
|
||||||
|
Number.parseInt(dateStr.slice(6, 8)),
|
||||||
|
Number.parseInt(dateStr.slice(8, 10)),
|
||||||
|
Number.parseInt(dateStr.slice(10, 12)),
|
||||||
|
Number.parseInt(dateStr.slice(12, 14)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error parsing certificate:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getExpirationStatus = (certData: string) => {
|
||||||
|
const expirationDate = extractExpirationDate(certData);
|
||||||
|
|
||||||
|
if (!expirationDate)
|
||||||
|
return {
|
||||||
|
status: "unknown" as const,
|
||||||
|
className: "text-muted-foreground",
|
||||||
|
message: "Could not determine expiration",
|
||||||
|
};
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const daysUntilExpiration = Math.ceil(
|
||||||
|
(expirationDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (daysUntilExpiration < 0) {
|
||||||
|
return {
|
||||||
|
status: "expired" as const,
|
||||||
|
className: "text-red-500",
|
||||||
|
message: `Expired on ${expirationDate.toLocaleDateString([], {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (daysUntilExpiration <= 30) {
|
||||||
|
return {
|
||||||
|
status: "warning" as const,
|
||||||
|
className: "text-yellow-500",
|
||||||
|
message: `Expires in ${daysUntilExpiration} days`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "valid" as const,
|
||||||
|
className: "text-muted-foreground",
|
||||||
|
message: `Expires ${expirationDate.toLocaleDateString([], {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})}`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCertificateChainInfo = (certData: string) => {
|
||||||
|
const certCount = (certData.match(/-----BEGIN CERTIFICATE-----/g) || [])
|
||||||
|
.length;
|
||||||
|
return certCount > 1
|
||||||
|
? {
|
||||||
|
isChain: true,
|
||||||
|
count: certCount,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
isChain: false,
|
||||||
|
count: 1,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<Card className="bg-transparent h-full">
|
<Card className="bg-transparent h-full">
|
||||||
@@ -23,7 +154,7 @@ export const ShowCertificates = () => {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2 pt-4 h-full">
|
<CardContent className="space-y-2 pt-4 h-full">
|
||||||
{data?.length === 0 ? (
|
{!data?.length ? (
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
<ShieldCheck className="size-8 self-center text-muted-foreground" />
|
<ShieldCheck className="size-8 self-center text-muted-foreground" />
|
||||||
<span className="text-base text-muted-foreground">
|
<span className="text-base text-muted-foreground">
|
||||||
@@ -35,21 +166,53 @@ export const ShowCertificates = () => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||||
{data?.map((destination, index) => (
|
{data.map((certificate, index) => {
|
||||||
<div
|
const expiration = getExpirationStatus(
|
||||||
key={destination.certificateId}
|
certificate.certificateData,
|
||||||
className="flex items-center justify-between border p-4 rounded-lg"
|
);
|
||||||
>
|
const chainInfo = getCertificateChainInfo(
|
||||||
<span className="text-sm text-muted-foreground">
|
certificate.certificateData,
|
||||||
{index + 1}. {destination.name}
|
);
|
||||||
</span>
|
return (
|
||||||
<div className="flex flex-row gap-3">
|
<div
|
||||||
<DeleteCertificate
|
key={certificate.certificateId}
|
||||||
certificateId={destination.certificateId}
|
className="flex flex-col border p-4 rounded-lg space-y-2"
|
||||||
/>
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{index + 1}. {certificate.name}
|
||||||
|
</span>
|
||||||
|
{chainInfo.isChain && (
|
||||||
|
<div className="flex items-center gap-1 px-1.5 py-0.5 rounded bg-muted/50">
|
||||||
|
<Link className="size-3 text-muted-foreground" />
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
Chain ({chainInfo.count})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<DeleteCertificate
|
||||||
|
certificateId={certificate.certificateId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`text-xs flex items-center gap-1.5 ${expiration.className}`}
|
||||||
|
>
|
||||||
|
{expiration.status !== "valid" && (
|
||||||
|
<AlertCircle className="size-3" />
|
||||||
|
)}
|
||||||
|
{expiration.message}
|
||||||
|
{certificate.autoRenew &&
|
||||||
|
expiration.status !== "valid" && (
|
||||||
|
<span className="text-xs text-emerald-500 ml-1">
|
||||||
|
(Auto-renewal enabled)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<AddCertificate />
|
<AddCertificate />
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { TrashIcon } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
@@ -24,8 +24,13 @@ export const DeleteNotification = ({ notificationId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button variant="ghost" isLoading={isLoading}>
|
<Button
|
||||||
<TrashIcon className="size-4 text-muted-foreground" />
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-9 w-9 group hover:bg-red-500/10"
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
<Trash2 className="size-4 text-muted-foreground group-hover:text-red-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
|
|||||||
@@ -40,48 +40,58 @@ export const ShowNotifications = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="grid lg:grid-cols-2 xl:grid-cols-3 gap-4">
|
<div className="grid lg:grid-cols-1 xl:grid-cols-2 gap-4">
|
||||||
{data?.map((notification, index) => (
|
{data?.map((notification, index) => (
|
||||||
<div
|
<div
|
||||||
key={notification.notificationId}
|
key={notification.notificationId}
|
||||||
className="flex items-center justify-between border gap-2 p-3.5 rounded-lg"
|
className="flex items-center justify-between rounded-xl p-4 transition-colors dark:bg-zinc-900/50 hover:bg-zinc-900 border border-zinc-800/50"
|
||||||
>
|
>
|
||||||
<div className="flex flex-row gap-2 items-center w-full ">
|
<div className="flex items-center gap-4">
|
||||||
{notification.notificationType === "slack" && (
|
{notification.notificationType === "slack" && (
|
||||||
<SlackIcon className="text-muted-foreground size-6 flex-shrink-0" />
|
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-indigo-500/10">
|
||||||
)}
|
<SlackIcon className="h-6 w-6 text-indigo-400" />
|
||||||
{notification.notificationType === "telegram" && (
|
</div>
|
||||||
<TelegramIcon className="text-muted-foreground size-8 flex-shrink-0" />
|
)}
|
||||||
)}
|
{notification.notificationType === "telegram" && (
|
||||||
{notification.notificationType === "discord" && (
|
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-cyan-500/10">
|
||||||
<DiscordIcon className="text-muted-foreground size-7 flex-shrink-0" />
|
<TelegramIcon className="h-6 w-6 text-indigo-400" />
|
||||||
)}
|
</div>
|
||||||
{notification.notificationType === "email" && (
|
)}
|
||||||
<Mail
|
{notification.notificationType === "discord" && (
|
||||||
size={29}
|
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-indigo-500/10">
|
||||||
className="text-muted-foreground size-6 flex-shrink-0"
|
<DiscordIcon className="h-6 w-6 text-indigo-400" />
|
||||||
/>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span className="text-sm text-muted-foreground">
|
{notification.notificationType === "email" && (
|
||||||
{notification.name}
|
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-zinc-500/10">
|
||||||
</span>
|
<Mail className="h-6 w-6 text-indigo-400" />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex flex-row gap-1 w-fit">
|
<div className="flex flex-col">
|
||||||
<UpdateNotification
|
<span className="text-sm font-medium text-zinc-300">
|
||||||
notificationId={notification.notificationId}
|
{notification.name}
|
||||||
/>
|
</span>
|
||||||
<DeleteNotification
|
<span className="text-xs font-medium text-muted-foreground">
|
||||||
notificationId={notification.notificationId}
|
{notification.notificationType?.[0]?.toUpperCase() + notification.notificationType?.slice(1)} notification
|
||||||
/>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div className="flex items-center gap-2">
|
||||||
</div>
|
<UpdateNotification
|
||||||
<div className="flex flex-col gap-4 justify-end w-full items-end">
|
notificationId={notification.notificationId}
|
||||||
<AddNotification />
|
/>
|
||||||
|
<DeleteNotification
|
||||||
|
notificationId={notification.notificationId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4 justify-end w-full items-end">
|
||||||
|
<AddNotification />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Mail, PenBoxIcon } from "lucide-react";
|
import { Mail, Pen } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { FieldErrors, useFieldArray, useForm } from "react-hook-form";
|
import { FieldErrors, useFieldArray, useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -218,8 +218,10 @@ export const UpdateNotification = ({ notificationId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
<Button variant="ghost">
|
<Button variant="ghost"
|
||||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
size="icon"
|
||||||
|
className="h-9 w-9">
|
||||||
|
<Pen className="size-4 text-muted-foreground" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ export const ProfileForm = () => {
|
|||||||
.then(async () => {
|
.then(async () => {
|
||||||
await refetch();
|
await refetch();
|
||||||
toast.success("Profile Updated");
|
toast.success("Profile Updated");
|
||||||
|
form.reset();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Error to Update the profile");
|
toast.error("Error to Update the profile");
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { Logo } from "../shared/logo";
|
|||||||
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
||||||
import { buttonVariants } from "../ui/button";
|
import { buttonVariants } from "../ui/button";
|
||||||
|
|
||||||
const AUTO_CHECK_UPDATES_INTERVAL_MINUTES = 15;
|
const AUTO_CHECK_UPDATES_INTERVAL_MINUTES = 5;
|
||||||
|
|
||||||
export const Navbar = () => {
|
export const Navbar = () => {
|
||||||
const [isUpdateAvailable, setIsUpdateAvailable] = useState<boolean>(false);
|
const [isUpdateAvailable, setIsUpdateAvailable] = useState<boolean>(false);
|
||||||
|
|||||||
@@ -188,9 +188,9 @@ export const authRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const currentAuth = await findAuthByEmail(ctx.user.email);
|
const currentAuth = await findAuthByEmail(ctx.user.email);
|
||||||
|
|
||||||
if (input.password) {
|
if (input.currentPassword || input.password) {
|
||||||
const correctPassword = bcrypt.compareSync(
|
const correctPassword = bcrypt.compareSync(
|
||||||
input.password,
|
input.currentPassword || "",
|
||||||
currentAuth?.password || "",
|
currentAuth?.password || "",
|
||||||
);
|
);
|
||||||
if (!correctPassword) {
|
if (!correctPassword) {
|
||||||
@@ -268,7 +268,9 @@ export const authRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return auth;
|
return auth;
|
||||||
}),
|
}),
|
||||||
|
verifyToken: protectedProcedure.mutation(async () => {
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
one: adminProcedure.input(apiFindOneAuth).query(async ({ input }) => {
|
one: adminProcedure.input(apiFindOneAuth).query(async ({ input }) => {
|
||||||
const auth = await findAuthById(input.id);
|
const auth = await findAuthById(input.id);
|
||||||
return auth;
|
return auth;
|
||||||
|
|||||||
@@ -269,11 +269,11 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
message: "You are not authorized to access this admin",
|
message: "You are not authorized to access this admin",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await updateAdmin(ctx.user.authId, {
|
const adminUpdated = await updateAdmin(ctx.user.authId, {
|
||||||
enableDockerCleanup: input.enableDockerCleanup,
|
enableDockerCleanup: input.enableDockerCleanup,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (admin.enableDockerCleanup) {
|
if (adminUpdated?.enableDockerCleanup) {
|
||||||
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
|
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
|
||||||
console.log(
|
console.log(
|
||||||
`Docker Cleanup ${new Date().toLocaleString()}] Running...`,
|
`Docker Cleanup ${new Date().toLocaleString()}] Running...`,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ services:
|
|||||||
hard: 262144
|
hard: 262144
|
||||||
|
|
||||||
plausible:
|
plausible:
|
||||||
image: ghcr.io/plausible/community-edition:v2.1.0
|
image: ghcr.io/plausible/community-edition:v2.1.4
|
||||||
restart: always
|
restart: always
|
||||||
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
|
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const templates: TemplateData[] = [
|
|||||||
{
|
{
|
||||||
id: "plausible",
|
id: "plausible",
|
||||||
name: "Plausible",
|
name: "Plausible",
|
||||||
version: "v2.1.0",
|
version: "v2.1.4",
|
||||||
description:
|
description:
|
||||||
"Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.",
|
"Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.",
|
||||||
logo: "plausible.svg",
|
logo: "plausible.svg",
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { runMariadbBackup } from "./mariadb";
|
|||||||
import { runMongoBackup } from "./mongo";
|
import { runMongoBackup } from "./mongo";
|
||||||
import { runMySqlBackup } from "./mysql";
|
import { runMySqlBackup } from "./mysql";
|
||||||
import { runPostgresBackup } from "./postgres";
|
import { runPostgresBackup } from "./postgres";
|
||||||
|
import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup";
|
||||||
|
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
||||||
|
|
||||||
export const initCronJobs = async () => {
|
export const initCronJobs = async () => {
|
||||||
console.log("Setting up cron jobs....");
|
console.log("Setting up cron jobs....");
|
||||||
@@ -25,14 +27,15 @@ export const initCronJobs = async () => {
|
|||||||
await cleanUpUnusedImages();
|
await cleanUpUnusedImages();
|
||||||
await cleanUpDockerBuilder();
|
await cleanUpDockerBuilder();
|
||||||
await cleanUpSystemPrune();
|
await cleanUpSystemPrune();
|
||||||
|
await sendDockerCleanupNotifications(admin.adminId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const servers = await getAllServers();
|
const servers = await getAllServers();
|
||||||
|
|
||||||
for (const server of servers) {
|
for (const server of servers) {
|
||||||
const { appName, serverId } = server;
|
const { appName, serverId, enableDockerCleanup } = server;
|
||||||
if (serverId) {
|
if (enableDockerCleanup) {
|
||||||
scheduleJob(serverId, "0 0 * * *", async () => {
|
scheduleJob(serverId, "0 0 * * *", async () => {
|
||||||
console.log(
|
console.log(
|
||||||
`SERVER-BACKUP[${new Date().toLocaleString()}] Running Cleanup ${appName}`,
|
`SERVER-BACKUP[${new Date().toLocaleString()}] Running Cleanup ${appName}`,
|
||||||
@@ -40,12 +43,17 @@ export const initCronJobs = async () => {
|
|||||||
await cleanUpUnusedImages(serverId);
|
await cleanUpUnusedImages(serverId);
|
||||||
await cleanUpDockerBuilder(serverId);
|
await cleanUpDockerBuilder(serverId);
|
||||||
await cleanUpSystemPrune(serverId);
|
await cleanUpSystemPrune(serverId);
|
||||||
|
await sendDockerCleanupNotifications(
|
||||||
|
admin.adminId,
|
||||||
|
`Docker cleanup for Server ${appName}`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pgs = await db.query.postgres.findMany({
|
const pgs = await db.query.postgres.findMany({
|
||||||
with: {
|
with: {
|
||||||
|
project: true,
|
||||||
backups: {
|
backups: {
|
||||||
with: {
|
with: {
|
||||||
destination: true,
|
destination: true,
|
||||||
@@ -61,18 +69,39 @@ export const initCronJobs = async () => {
|
|||||||
for (const backup of pg.backups) {
|
for (const backup of pg.backups) {
|
||||||
const { schedule, backupId, enabled } = backup;
|
const { schedule, backupId, enabled } = backup;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
scheduleJob(backupId, schedule, async () => {
|
try {
|
||||||
console.log(
|
scheduleJob(backupId, schedule, async () => {
|
||||||
`PG-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
|
console.log(
|
||||||
);
|
`PG-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
|
||||||
runPostgresBackup(pg, backup);
|
);
|
||||||
});
|
runPostgresBackup(pg, backup);
|
||||||
|
});
|
||||||
|
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: pg.name,
|
||||||
|
projectName: pg.project.name,
|
||||||
|
databaseType: "postgres",
|
||||||
|
type: "success",
|
||||||
|
adminId: pg.project.adminId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: pg.name,
|
||||||
|
projectName: pg.project.name,
|
||||||
|
databaseType: "postgres",
|
||||||
|
type: "error",
|
||||||
|
// @ts-ignore
|
||||||
|
errorMessage: error?.message || "Error message not provided",
|
||||||
|
adminId: pg.project.adminId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mariadbs = await db.query.mariadb.findMany({
|
const mariadbs = await db.query.mariadb.findMany({
|
||||||
with: {
|
with: {
|
||||||
|
project: true,
|
||||||
backups: {
|
backups: {
|
||||||
with: {
|
with: {
|
||||||
destination: true,
|
destination: true,
|
||||||
@@ -89,18 +118,38 @@ export const initCronJobs = async () => {
|
|||||||
for (const backup of maria.backups) {
|
for (const backup of maria.backups) {
|
||||||
const { schedule, backupId, enabled } = backup;
|
const { schedule, backupId, enabled } = backup;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
scheduleJob(backupId, schedule, async () => {
|
try {
|
||||||
console.log(
|
scheduleJob(backupId, schedule, async () => {
|
||||||
`MARIADB-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
|
console.log(
|
||||||
);
|
`MARIADB-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
|
||||||
await runMariadbBackup(maria, backup);
|
);
|
||||||
});
|
await runMariadbBackup(maria, backup);
|
||||||
|
});
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: maria.name,
|
||||||
|
projectName: maria.project.name,
|
||||||
|
databaseType: "mariadb",
|
||||||
|
type: "success",
|
||||||
|
adminId: maria.project.adminId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: maria.name,
|
||||||
|
projectName: maria.project.name,
|
||||||
|
databaseType: "mariadb",
|
||||||
|
type: "error",
|
||||||
|
// @ts-ignore
|
||||||
|
errorMessage: error?.message || "Error message not provided",
|
||||||
|
adminId: maria.project.adminId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mongodbs = await db.query.mongo.findMany({
|
const mongodbs = await db.query.mongo.findMany({
|
||||||
with: {
|
with: {
|
||||||
|
project: true,
|
||||||
backups: {
|
backups: {
|
||||||
with: {
|
with: {
|
||||||
destination: true,
|
destination: true,
|
||||||
@@ -117,18 +166,38 @@ export const initCronJobs = async () => {
|
|||||||
for (const backup of mongo.backups) {
|
for (const backup of mongo.backups) {
|
||||||
const { schedule, backupId, enabled } = backup;
|
const { schedule, backupId, enabled } = backup;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
scheduleJob(backupId, schedule, async () => {
|
try {
|
||||||
console.log(
|
scheduleJob(backupId, schedule, async () => {
|
||||||
`MONGO-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
|
console.log(
|
||||||
);
|
`MONGO-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
|
||||||
await runMongoBackup(mongo, backup);
|
);
|
||||||
});
|
await runMongoBackup(mongo, backup);
|
||||||
|
});
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: mongo.name,
|
||||||
|
projectName: mongo.project.name,
|
||||||
|
databaseType: "mongodb",
|
||||||
|
type: "success",
|
||||||
|
adminId: mongo.project.adminId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: mongo.name,
|
||||||
|
projectName: mongo.project.name,
|
||||||
|
databaseType: "mongodb",
|
||||||
|
type: "error",
|
||||||
|
// @ts-ignore
|
||||||
|
errorMessage: error?.message || "Error message not provided",
|
||||||
|
adminId: mongo.project.adminId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mysqls = await db.query.mysql.findMany({
|
const mysqls = await db.query.mysql.findMany({
|
||||||
with: {
|
with: {
|
||||||
|
project: true,
|
||||||
backups: {
|
backups: {
|
||||||
with: {
|
with: {
|
||||||
destination: true,
|
destination: true,
|
||||||
@@ -145,12 +214,31 @@ export const initCronJobs = async () => {
|
|||||||
for (const backup of mysql.backups) {
|
for (const backup of mysql.backups) {
|
||||||
const { schedule, backupId, enabled } = backup;
|
const { schedule, backupId, enabled } = backup;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
scheduleJob(backupId, schedule, async () => {
|
try {
|
||||||
console.log(
|
scheduleJob(backupId, schedule, async () => {
|
||||||
`MYSQL-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
|
console.log(
|
||||||
);
|
`MYSQL-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`,
|
||||||
await runMySqlBackup(mysql, backup);
|
);
|
||||||
});
|
await runMySqlBackup(mysql, backup);
|
||||||
|
});
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: mysql.name,
|
||||||
|
projectName: mysql.project.name,
|
||||||
|
databaseType: "mysql",
|
||||||
|
type: "success",
|
||||||
|
adminId: mysql.project.adminId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: mysql.name,
|
||||||
|
projectName: mysql.project.name,
|
||||||
|
databaseType: "mysql",
|
||||||
|
type: "error",
|
||||||
|
// @ts-ignore
|
||||||
|
errorMessage: error?.message || "Error message not provided",
|
||||||
|
adminId: mysql.project.adminId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user