mirror of
https://github.com/andwati/dokploy.git
synced 2026-10-30 00:32:16 +00:00
Merge branch 'canary' into filter-projects-shortcut
This commit is contained in:
@@ -3,8 +3,8 @@ import { z } from "zod";
|
|||||||
export const deployJobSchema = z.discriminatedUnion("applicationType", [
|
export const deployJobSchema = z.discriminatedUnion("applicationType", [
|
||||||
z.object({
|
z.object({
|
||||||
applicationId: z.string(),
|
applicationId: z.string(),
|
||||||
titleLog: z.string(),
|
titleLog: z.string().optional(),
|
||||||
descriptionLog: z.string(),
|
descriptionLog: z.string().optional(),
|
||||||
server: z.boolean().optional(),
|
server: z.boolean().optional(),
|
||||||
type: z.enum(["deploy", "redeploy"]),
|
type: z.enum(["deploy", "redeploy"]),
|
||||||
applicationType: z.literal("application"),
|
applicationType: z.literal("application"),
|
||||||
@@ -12,8 +12,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
composeId: z.string(),
|
composeId: z.string(),
|
||||||
titleLog: z.string(),
|
titleLog: z.string().optional(),
|
||||||
descriptionLog: z.string(),
|
descriptionLog: z.string().optional(),
|
||||||
server: z.boolean().optional(),
|
server: z.boolean().optional(),
|
||||||
type: z.enum(["deploy", "redeploy"]),
|
type: z.enum(["deploy", "redeploy"]),
|
||||||
applicationType: z.literal("compose"),
|
applicationType: z.literal("compose"),
|
||||||
@@ -22,8 +22,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
|
|||||||
z.object({
|
z.object({
|
||||||
applicationId: z.string(),
|
applicationId: z.string(),
|
||||||
previewDeploymentId: z.string(),
|
previewDeploymentId: z.string(),
|
||||||
titleLog: z.string(),
|
titleLog: z.string().optional(),
|
||||||
descriptionLog: z.string(),
|
descriptionLog: z.string().optional(),
|
||||||
server: z.boolean().optional(),
|
server: z.boolean().optional(),
|
||||||
type: z.enum(["deploy"]),
|
type: z.enum(["deploy"]),
|
||||||
applicationType: z.literal("application-preview"),
|
applicationType: z.literal("application-preview"),
|
||||||
|
|||||||
+10
-10
@@ -18,14 +18,14 @@ export const deploy = async (job: DeployJob) => {
|
|||||||
if (job.type === "redeploy") {
|
if (job.type === "redeploy") {
|
||||||
await rebuildRemoteApplication({
|
await rebuildRemoteApplication({
|
||||||
applicationId: job.applicationId,
|
applicationId: job.applicationId,
|
||||||
titleLog: job.titleLog,
|
titleLog: job.titleLog || "Rebuild deployment",
|
||||||
descriptionLog: job.descriptionLog,
|
descriptionLog: job.descriptionLog || "",
|
||||||
});
|
});
|
||||||
} else if (job.type === "deploy") {
|
} else if (job.type === "deploy") {
|
||||||
await deployRemoteApplication({
|
await deployRemoteApplication({
|
||||||
applicationId: job.applicationId,
|
applicationId: job.applicationId,
|
||||||
titleLog: job.titleLog,
|
titleLog: job.titleLog || "Manual deployment",
|
||||||
descriptionLog: job.descriptionLog,
|
descriptionLog: job.descriptionLog || "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,14 +38,14 @@ export const deploy = async (job: DeployJob) => {
|
|||||||
if (job.type === "redeploy") {
|
if (job.type === "redeploy") {
|
||||||
await rebuildRemoteCompose({
|
await rebuildRemoteCompose({
|
||||||
composeId: job.composeId,
|
composeId: job.composeId,
|
||||||
titleLog: job.titleLog,
|
titleLog: job.titleLog || "Rebuild deployment",
|
||||||
descriptionLog: job.descriptionLog,
|
descriptionLog: job.descriptionLog || "",
|
||||||
});
|
});
|
||||||
} else if (job.type === "deploy") {
|
} else if (job.type === "deploy") {
|
||||||
await deployRemoteCompose({
|
await deployRemoteCompose({
|
||||||
composeId: job.composeId,
|
composeId: job.composeId,
|
||||||
titleLog: job.titleLog,
|
titleLog: job.titleLog || "Manual deployment",
|
||||||
descriptionLog: job.descriptionLog,
|
descriptionLog: job.descriptionLog || "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,8 @@ export const deploy = async (job: DeployJob) => {
|
|||||||
if (job.type === "deploy") {
|
if (job.type === "deploy") {
|
||||||
await deployRemotePreviewApplication({
|
await deployRemotePreviewApplication({
|
||||||
applicationId: job.applicationId,
|
applicationId: job.applicationId,
|
||||||
titleLog: job.titleLog,
|
titleLog: job.titleLog || "Preview Deployment",
|
||||||
descriptionLog: job.descriptionLog,
|
descriptionLog: job.descriptionLog || "",
|
||||||
previewDeploymentId: job.previewDeploymentId,
|
previewDeploymentId: job.previewDeploymentId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
|
|
||||||
const hasServers = servers && servers.length > 0;
|
const hasServers = servers && servers.length > 0;
|
||||||
|
// Show dropdown logic based on cloud environment
|
||||||
|
// Cloud: show only if there are remote servers (no Dokploy option)
|
||||||
|
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
||||||
|
const shouldShowServerDropdown = hasServers;
|
||||||
|
|
||||||
const { mutateAsync, isLoading, error, isError } =
|
const { mutateAsync, isLoading, error, isError } =
|
||||||
api.application.create.useMutation();
|
api.application.create.useMutation();
|
||||||
@@ -94,8 +98,8 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
appName: data.appName,
|
appName: data.appName,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
|
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
||||||
environmentId,
|
environmentId,
|
||||||
serverId: data.serverId,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Service Created");
|
toast.success("Service Created");
|
||||||
@@ -157,7 +161,7 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{hasServers && (
|
{shouldShowServerDropdown && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="serverId"
|
name="serverId"
|
||||||
@@ -186,13 +190,27 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={field.value}
|
defaultValue={
|
||||||
|
field.value || (!isCloud ? "dokploy" : undefined)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select a Server" />
|
<SelectValue
|
||||||
|
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
||||||
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
|
{!isCloud && (
|
||||||
|
<SelectItem value="dokploy">
|
||||||
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
|
<span>Dokploy</span>
|
||||||
|
<span className="text-muted-foreground text-xs self-center">
|
||||||
|
Default
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={server.serverId}
|
key={server.serverId}
|
||||||
@@ -206,7 +224,9 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
</span>
|
</span>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
<SelectLabel>
|
||||||
|
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
||||||
|
</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
const { data: environment } = api.environment.one.useQuery({ environmentId });
|
const { data: environment } = api.environment.one.useQuery({ environmentId });
|
||||||
|
|
||||||
const hasServers = servers && servers.length > 0;
|
const hasServers = servers && servers.length > 0;
|
||||||
|
// Show dropdown logic based on cloud environment
|
||||||
|
// Cloud: show only if there are remote servers (no Dokploy option)
|
||||||
|
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
||||||
|
const shouldShowServerDropdown = hasServers;
|
||||||
|
|
||||||
const form = useForm<AddCompose>({
|
const form = useForm<AddCompose>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -104,7 +108,7 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
environmentId,
|
environmentId,
|
||||||
composeType: data.composeType,
|
composeType: data.composeType,
|
||||||
appName: data.appName,
|
appName: data.appName,
|
||||||
serverId: data.serverId,
|
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Compose Created");
|
toast.success("Compose Created");
|
||||||
@@ -169,7 +173,7 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{hasServers && (
|
{shouldShowServerDropdown && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="serverId"
|
name="serverId"
|
||||||
@@ -198,13 +202,27 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={field.value}
|
defaultValue={
|
||||||
|
field.value || (!isCloud ? "dokploy" : undefined)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select a Server" />
|
<SelectValue
|
||||||
|
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
||||||
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
|
{!isCloud && (
|
||||||
|
<SelectItem value="dokploy">
|
||||||
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
|
<span>Dokploy</span>
|
||||||
|
<span className="text-muted-foreground text-xs self-center">
|
||||||
|
Default
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={server.serverId}
|
key={server.serverId}
|
||||||
@@ -218,7 +236,9 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
</span>
|
</span>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
<SelectLabel>
|
||||||
|
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
||||||
|
</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const slug = slugify(projectName);
|
const slug = slugify(projectName);
|
||||||
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
const postgresMutation = api.postgres.create.useMutation();
|
const postgresMutation = api.postgres.create.useMutation();
|
||||||
const mongoMutation = api.mongo.create.useMutation();
|
const mongoMutation = api.mongo.create.useMutation();
|
||||||
@@ -190,6 +191,10 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
const { data: environment } = api.environment.one.useQuery({ environmentId });
|
const { data: environment } = api.environment.one.useQuery({ environmentId });
|
||||||
|
|
||||||
const hasServers = servers && servers.length > 0;
|
const hasServers = servers && servers.length > 0;
|
||||||
|
// Show dropdown logic based on cloud environment
|
||||||
|
// Cloud: show only if there are remote servers (no Dokploy option)
|
||||||
|
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
||||||
|
const shouldShowServerDropdown = hasServers;
|
||||||
|
|
||||||
const form = useForm<AddDatabase>({
|
const form = useForm<AddDatabase>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -223,9 +228,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
appName: data.appName,
|
appName: data.appName,
|
||||||
dockerImage: defaultDockerImage,
|
dockerImage: defaultDockerImage,
|
||||||
projectId: environment?.projectId || "",
|
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
||||||
environmentId,
|
environmentId,
|
||||||
serverId: data.serverId,
|
|
||||||
description: data.description,
|
description: data.description,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -237,7 +241,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
|
|
||||||
databaseUser:
|
databaseUser:
|
||||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
serverId: data.serverId,
|
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
||||||
});
|
});
|
||||||
} else if (data.type === "mongo") {
|
} else if (data.type === "mongo") {
|
||||||
promise = mongoMutation.mutateAsync({
|
promise = mongoMutation.mutateAsync({
|
||||||
@@ -245,14 +249,14 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
databasePassword: data.databasePassword,
|
databasePassword: data.databasePassword,
|
||||||
databaseUser:
|
databaseUser:
|
||||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
serverId: data.serverId,
|
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
||||||
replicaSets: data.replicaSets,
|
replicaSets: data.replicaSets,
|
||||||
});
|
});
|
||||||
} else if (data.type === "redis") {
|
} else if (data.type === "redis") {
|
||||||
promise = redisMutation.mutateAsync({
|
promise = redisMutation.mutateAsync({
|
||||||
...commonParams,
|
...commonParams,
|
||||||
databasePassword: data.databasePassword,
|
databasePassword: data.databasePassword,
|
||||||
serverId: data.serverId,
|
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
||||||
});
|
});
|
||||||
} else if (data.type === "mariadb") {
|
} else if (data.type === "mariadb") {
|
||||||
promise = mariadbMutation.mutateAsync({
|
promise = mariadbMutation.mutateAsync({
|
||||||
@@ -262,7 +266,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
databaseName: data.databaseName || "mariadb",
|
databaseName: data.databaseName || "mariadb",
|
||||||
databaseUser:
|
databaseUser:
|
||||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
serverId: data.serverId,
|
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
||||||
});
|
});
|
||||||
} else if (data.type === "mysql") {
|
} else if (data.type === "mysql") {
|
||||||
promise = mysqlMutation.mutateAsync({
|
promise = mysqlMutation.mutateAsync({
|
||||||
@@ -271,8 +275,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
databaseName: data.databaseName || "mysql",
|
databaseName: data.databaseName || "mysql",
|
||||||
databaseUser:
|
databaseUser:
|
||||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
|
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
||||||
databaseRootPassword: data.databaseRootPassword || "",
|
databaseRootPassword: data.databaseRootPassword || "",
|
||||||
serverId: data.serverId,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +407,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{hasServers && (
|
{shouldShowServerDropdown && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="serverId"
|
name="serverId"
|
||||||
@@ -412,13 +416,29 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
<FormLabel>Select a Server</FormLabel>
|
<FormLabel>Select a Server</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={field.value || ""}
|
defaultValue={
|
||||||
|
field.value || (!isCloud ? "dokploy" : undefined)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select a Server" />
|
<SelectValue
|
||||||
|
placeholder={
|
||||||
|
!isCloud ? "Dokploy" : "Select a Server"
|
||||||
|
}
|
||||||
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
|
{!isCloud && (
|
||||||
|
<SelectItem value="dokploy">
|
||||||
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
|
<span>Dokploy</span>
|
||||||
|
<span className="text-muted-foreground text-xs self-center">
|
||||||
|
Default
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={server.serverId}
|
key={server.serverId}
|
||||||
@@ -428,7 +448,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>
|
<SelectLabel>
|
||||||
Servers ({servers?.length})
|
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
@@ -141,6 +141,10 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
|||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
const hasServers = servers && servers.length > 0;
|
const hasServers = servers && servers.length > 0;
|
||||||
|
// Show dropdown logic based on cloud environment
|
||||||
|
// Cloud: show only if there are remote servers (no Dokploy option)
|
||||||
|
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
||||||
|
const shouldShowServerDropdown = hasServers;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
@@ -430,7 +434,7 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
|||||||
project.
|
project.
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
|
|
||||||
{hasServers && (
|
{shouldShowServerDropdown && (
|
||||||
<div>
|
<div>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -459,12 +463,29 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
|||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setServerId(e);
|
setServerId(e);
|
||||||
}}
|
}}
|
||||||
|
defaultValue={
|
||||||
|
!isCloud ? "dokploy" : undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select a Server" />
|
<SelectValue
|
||||||
|
placeholder={
|
||||||
|
!isCloud ? "Dokploy" : "Select a Server"
|
||||||
|
}
|
||||||
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
|
{!isCloud && (
|
||||||
|
<SelectItem value="dokploy">
|
||||||
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
|
<span>Dokploy</span>
|
||||||
|
<span className="text-muted-foreground text-xs self-center">
|
||||||
|
Default
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={server.serverId}
|
key={server.serverId}
|
||||||
@@ -479,7 +500,8 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
|||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>
|
<SelectLabel>
|
||||||
Servers ({servers?.length})
|
Servers (
|
||||||
|
{servers?.length + (!isCloud ? 1 : 0)})
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -493,8 +515,11 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const promise = mutateAsync({
|
const promise = mutateAsync({
|
||||||
|
serverId:
|
||||||
|
serverId === "dokploy"
|
||||||
|
? undefined
|
||||||
|
: serverId,
|
||||||
environmentId,
|
environmentId,
|
||||||
serverId: serverId || undefined,
|
|
||||||
id: template.id,
|
id: template.id,
|
||||||
baseUrl: customBaseUrl,
|
baseUrl: customBaseUrl,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ const examples = [
|
|||||||
export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
|
export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
|
||||||
// Get servers from the API
|
// Get servers from the API
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
const hasServers = servers && servers.length > 0;
|
const hasServers = servers && servers.length > 0;
|
||||||
|
// Show dropdown logic based on cloud environment
|
||||||
|
// Cloud: show only if there are remote servers (no Dokploy option)
|
||||||
|
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
||||||
|
const shouldShowServerDropdown = hasServers;
|
||||||
|
|
||||||
const handleExampleClick = (example: string) => {
|
const handleExampleClick = (example: string) => {
|
||||||
setTemplateInfo({ ...templateInfo, userInput: example });
|
setTemplateInfo({ ...templateInfo, userInput: example });
|
||||||
@@ -48,34 +53,58 @@ export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasServers && (
|
{shouldShowServerDropdown && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="server-deploy">
|
<Label htmlFor="server-deploy">
|
||||||
Select the server where you want to deploy (optional)
|
Select the server where you want to deploy (optional)
|
||||||
</Label>
|
</Label>
|
||||||
<Select
|
<Select
|
||||||
value={templateInfo.server?.serverId}
|
value={
|
||||||
|
templateInfo.server?.serverId ||
|
||||||
|
(!isCloud ? "dokploy" : undefined)
|
||||||
|
}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const server = servers?.find((s) => s.serverId === value);
|
if (value === "dokploy") {
|
||||||
if (server) {
|
|
||||||
setTemplateInfo({
|
setTemplateInfo({
|
||||||
...templateInfo,
|
...templateInfo,
|
||||||
server: server,
|
server: undefined,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
const server = servers?.find((s) => s.serverId === value);
|
||||||
|
if (server) {
|
||||||
|
setTemplateInfo({
|
||||||
|
...templateInfo,
|
||||||
|
server: server,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-full">
|
<SelectTrigger className="w-full">
|
||||||
<SelectValue placeholder="Select a server" />
|
<SelectValue
|
||||||
|
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
||||||
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
|
{!isCloud && (
|
||||||
|
<SelectItem value="dokploy">
|
||||||
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
|
<span>Dokploy</span>
|
||||||
|
<span className="text-muted-foreground text-xs self-center">
|
||||||
|
Default
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem key={server.serverId} value={server.serverId}>
|
<SelectItem key={server.serverId} value={server.serverId}>
|
||||||
{server.name}
|
{server.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
<SelectLabel>
|
||||||
|
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
||||||
|
</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ export const AddCertificate = () => {
|
|||||||
const { mutateAsync, isError, error, isLoading } =
|
const { mutateAsync, isError, error, isLoading } =
|
||||||
api.certificates.create.useMutation();
|
api.certificates.create.useMutation();
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
|
const hasServers = servers && servers.length > 0;
|
||||||
|
// Show dropdown logic based on cloud environment
|
||||||
|
// Cloud: show only if there are remote servers (no Dokploy option)
|
||||||
|
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
||||||
|
const shouldShowServerDropdown = hasServers;
|
||||||
|
|
||||||
const form = useForm<AddCertificate>({
|
const form = useForm<AddCertificate>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -85,7 +90,7 @@ export const AddCertificate = () => {
|
|||||||
certificateData: data.certificateData,
|
certificateData: data.certificateData,
|
||||||
privateKey: data.privateKey,
|
privateKey: data.privateKey,
|
||||||
autoRenew: data.autoRenew,
|
autoRenew: data.autoRenew,
|
||||||
serverId: data.serverId,
|
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
||||||
organizationId: "",
|
organizationId: "",
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
@@ -174,52 +179,70 @@ export const AddCertificate = () => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
{shouldShowServerDropdown && (
|
||||||
control={form.control}
|
<FormField
|
||||||
name="serverId"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="serverId"
|
||||||
<FormItem>
|
render={({ field }) => (
|
||||||
<TooltipProvider delayDuration={0}>
|
<FormItem>
|
||||||
<Tooltip>
|
<TooltipProvider delayDuration={0}>
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
<TooltipTrigger asChild>
|
||||||
Select a Server {!isCloud && "(Optional)"}
|
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
||||||
<HelpCircle className="size-4 text-muted-foreground" />
|
Select a Server {!isCloud && "(Optional)"}
|
||||||
</FormLabel>
|
<HelpCircle className="size-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</FormLabel>
|
||||||
</Tooltip>
|
</TooltipTrigger>
|
||||||
</TooltipProvider>
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={field.value}
|
defaultValue={
|
||||||
>
|
field.value || (!isCloud ? "dokploy" : undefined)
|
||||||
<SelectTrigger>
|
}
|
||||||
<SelectValue placeholder="Select a Server" />
|
>
|
||||||
</SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectContent>
|
<SelectValue
|
||||||
<SelectGroup>
|
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
||||||
{servers?.map((server) => (
|
/>
|
||||||
<SelectItem
|
</SelectTrigger>
|
||||||
key={server.serverId}
|
<SelectContent>
|
||||||
value={server.serverId}
|
<SelectGroup>
|
||||||
>
|
{!isCloud && (
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
<SelectItem value="dokploy">
|
||||||
<span>{server.name}</span>
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
<span className="text-muted-foreground text-xs self-center">
|
<span>Dokploy</span>
|
||||||
{server.ipAddress}
|
<span className="text-muted-foreground text-xs self-center">
|
||||||
|
Default
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</SelectItem>
|
||||||
</SelectItem>
|
)}
|
||||||
))}
|
{servers?.map((server) => (
|
||||||
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
<SelectItem
|
||||||
</SelectGroup>
|
key={server.serverId}
|
||||||
</SelectContent>
|
value={server.serverId}
|
||||||
</Select>
|
>
|
||||||
<FormMessage />
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
</FormItem>
|
<span>{server.name}</span>
|
||||||
)}
|
<span className="text-muted-foreground text-xs self-center">
|
||||||
/>
|
{server.ipAddress}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectLabel>
|
||||||
|
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
||||||
|
</SelectLabel>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<DialogFooter className="flex w-full flex-row !justify-end">
|
<DialogFooter className="flex w-full flex-row !justify-end">
|
||||||
|
|||||||
@@ -101,6 +101,15 @@ export const notificationSchema = z.discriminatedUnion("type", [
|
|||||||
decoration: z.boolean().default(true),
|
decoration: z.boolean().default(true),
|
||||||
})
|
})
|
||||||
.merge(notificationBaseSchema),
|
.merge(notificationBaseSchema),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
type: z.literal("ntfy"),
|
||||||
|
serverUrl: z.string().min(1, { message: "Server URL is required" }),
|
||||||
|
topic: z.string().min(1, { message: "Topic is required" }),
|
||||||
|
accessToken: z.string().min(1, { message: "Access Token is required" }),
|
||||||
|
priority: z.number().min(1).max(5).default(3),
|
||||||
|
})
|
||||||
|
.merge(notificationBaseSchema),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const notificationsMap = {
|
export const notificationsMap = {
|
||||||
@@ -124,6 +133,10 @@ export const notificationsMap = {
|
|||||||
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
|
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
|
||||||
label: "Gotify",
|
label: "Gotify",
|
||||||
},
|
},
|
||||||
|
ntfy: {
|
||||||
|
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
|
||||||
|
label: "ntfy",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotificationSchema = z.infer<typeof notificationSchema>;
|
export type NotificationSchema = z.infer<typeof notificationSchema>;
|
||||||
@@ -155,6 +168,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
api.notification.testEmailConnection.useMutation();
|
api.notification.testEmailConnection.useMutation();
|
||||||
const { mutateAsync: testGotifyConnection, isLoading: isLoadingGotify } =
|
const { mutateAsync: testGotifyConnection, isLoading: isLoadingGotify } =
|
||||||
api.notification.testGotifyConnection.useMutation();
|
api.notification.testGotifyConnection.useMutation();
|
||||||
|
const { mutateAsync: testNtfyConnection, isLoading: isLoadingNtfy } =
|
||||||
|
api.notification.testNtfyConnection.useMutation();
|
||||||
const slackMutation = notificationId
|
const slackMutation = notificationId
|
||||||
? api.notification.updateSlack.useMutation()
|
? api.notification.updateSlack.useMutation()
|
||||||
: api.notification.createSlack.useMutation();
|
: api.notification.createSlack.useMutation();
|
||||||
@@ -170,6 +185,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
const gotifyMutation = notificationId
|
const gotifyMutation = notificationId
|
||||||
? api.notification.updateGotify.useMutation()
|
? api.notification.updateGotify.useMutation()
|
||||||
: api.notification.createGotify.useMutation();
|
: api.notification.createGotify.useMutation();
|
||||||
|
const ntfyMutation = notificationId
|
||||||
|
? api.notification.updateNtfy.useMutation()
|
||||||
|
: api.notification.createNtfy.useMutation();
|
||||||
|
|
||||||
const form = useForm<NotificationSchema>({
|
const form = useForm<NotificationSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -266,6 +284,20 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
name: notification.name,
|
name: notification.name,
|
||||||
dockerCleanup: notification.dockerCleanup,
|
dockerCleanup: notification.dockerCleanup,
|
||||||
});
|
});
|
||||||
|
} else if (notification.notificationType === "ntfy") {
|
||||||
|
form.reset({
|
||||||
|
appBuildError: notification.appBuildError,
|
||||||
|
appDeploy: notification.appDeploy,
|
||||||
|
dokployRestart: notification.dokployRestart,
|
||||||
|
databaseBackup: notification.databaseBackup,
|
||||||
|
type: notification.notificationType,
|
||||||
|
accessToken: notification.ntfy?.accessToken,
|
||||||
|
topic: notification.ntfy?.topic,
|
||||||
|
priority: notification.ntfy?.priority,
|
||||||
|
serverUrl: notification.ntfy?.serverUrl,
|
||||||
|
name: notification.name,
|
||||||
|
dockerCleanup: notification.dockerCleanup,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
form.reset();
|
form.reset();
|
||||||
@@ -278,6 +310,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
discord: discordMutation,
|
discord: discordMutation,
|
||||||
email: emailMutation,
|
email: emailMutation,
|
||||||
gotify: gotifyMutation,
|
gotify: gotifyMutation,
|
||||||
|
ntfy: ntfyMutation,
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (data: NotificationSchema) => {
|
const onSubmit = async (data: NotificationSchema) => {
|
||||||
@@ -366,6 +399,21 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
notificationId: notificationId || "",
|
notificationId: notificationId || "",
|
||||||
gotifyId: notification?.gotifyId || "",
|
gotifyId: notification?.gotifyId || "",
|
||||||
});
|
});
|
||||||
|
} else if (data.type === "ntfy") {
|
||||||
|
promise = ntfyMutation.mutateAsync({
|
||||||
|
appBuildError: appBuildError,
|
||||||
|
appDeploy: appDeploy,
|
||||||
|
dokployRestart: dokployRestart,
|
||||||
|
databaseBackup: databaseBackup,
|
||||||
|
serverUrl: data.serverUrl,
|
||||||
|
accessToken: data.accessToken,
|
||||||
|
topic: data.topic,
|
||||||
|
priority: data.priority,
|
||||||
|
name: data.name,
|
||||||
|
dockerCleanup: dockerCleanup,
|
||||||
|
notificationId: notificationId || "",
|
||||||
|
ntfyId: notification?.ntfyId || "",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promise) {
|
if (promise) {
|
||||||
@@ -875,6 +923,83 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{type === "ntfy" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="serverUrl"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Server URL</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="https://ntfy.sh" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="topic"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Topic</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="deployments" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="accessToken"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Access Token</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="AzxcvbnmKjhgfdsa..."
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="priority"
|
||||||
|
defaultValue={3}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="w-full">
|
||||||
|
<FormLabel>Priority</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="3"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
if (value) {
|
||||||
|
const port = Number.parseInt(value);
|
||||||
|
if (port > 0 && port <= 5) {
|
||||||
|
field.onChange(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Message priority (1-5, default: 3)
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
@@ -1024,7 +1149,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
isLoadingTelegram ||
|
isLoadingTelegram ||
|
||||||
isLoadingDiscord ||
|
isLoadingDiscord ||
|
||||||
isLoadingEmail ||
|
isLoadingEmail ||
|
||||||
isLoadingGotify
|
isLoadingGotify ||
|
||||||
|
isLoadingNtfy
|
||||||
}
|
}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -1061,6 +1187,13 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
priority: form.getValues("priority"),
|
priority: form.getValues("priority"),
|
||||||
decoration: form.getValues("decoration"),
|
decoration: form.getValues("decoration"),
|
||||||
});
|
});
|
||||||
|
} else if (type === "ntfy") {
|
||||||
|
await testNtfyConnection({
|
||||||
|
serverUrl: form.getValues("serverUrl"),
|
||||||
|
topic: form.getValues("topic"),
|
||||||
|
accessToken: form.getValues("accessToken"),
|
||||||
|
priority: form.getValues("priority"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
toast.success("Connection Success");
|
toast.success("Connection Success");
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ export const ShowNotifications = () => {
|
|||||||
<MessageCircleMore className="size-6 text-muted-foreground" />
|
<MessageCircleMore className="size-6 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{notification.notificationType === "ntfy" && (
|
||||||
|
<div className="flex items-center justify-center rounded-lg ">
|
||||||
|
<MessageCircleMore className="size-6 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{notification.name}
|
{notification.name}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
ALTER TYPE "public"."notificationType" ADD VALUE 'ntfy';--> statement-breakpoint
|
||||||
|
CREATE TABLE "ntfy" (
|
||||||
|
"ntfyId" text PRIMARY KEY NOT NULL,
|
||||||
|
"serverUrl" text NOT NULL,
|
||||||
|
"topic" text NOT NULL,
|
||||||
|
"accessToken" text NOT NULL,
|
||||||
|
"priority" integer DEFAULT 3 NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "notification" ADD COLUMN "ntfyId" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "notification" ADD CONSTRAINT "notification_ntfyId_ntfy_ntfyId_fk" FOREIGN KEY ("ntfyId") REFERENCES "public"."ntfy"("ntfyId") ON DELETE cascade ON UPDATE no action;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -771,6 +771,13 @@
|
|||||||
"when": 1757052053574,
|
"when": 1757052053574,
|
||||||
"tag": "0109_remarkable_sauron",
|
"tag": "0109_remarkable_sauron",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 110,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1757189541734,
|
||||||
|
"tag": "0110_red_psynapse",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -40,8 +40,10 @@ import {
|
|||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import {
|
import {
|
||||||
apiCreateApplication,
|
apiCreateApplication,
|
||||||
|
apiDeployApplication,
|
||||||
apiFindMonitoringStats,
|
apiFindMonitoringStats,
|
||||||
apiFindOneApplication,
|
apiFindOneApplication,
|
||||||
|
apiRedeployApplication,
|
||||||
apiReloadApplication,
|
apiReloadApplication,
|
||||||
apiSaveBitbucketProvider,
|
apiSaveBitbucketProvider,
|
||||||
apiSaveBuildType,
|
apiSaveBuildType,
|
||||||
@@ -306,7 +308,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
redeploy: protectedProcedure
|
redeploy: protectedProcedure
|
||||||
.input(apiFindOneApplication)
|
.input(apiRedeployApplication)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
@@ -320,8 +322,8 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
const jobData: DeploymentJob = {
|
const jobData: DeploymentJob = {
|
||||||
applicationId: input.applicationId,
|
applicationId: input.applicationId,
|
||||||
titleLog: "Rebuild deployment",
|
titleLog: input.title || "Rebuild deployment",
|
||||||
descriptionLog: "",
|
descriptionLog: input.description || "",
|
||||||
type: "redeploy",
|
type: "redeploy",
|
||||||
applicationType: "application",
|
applicationType: "application",
|
||||||
server: !!application.serverId,
|
server: !!application.serverId,
|
||||||
@@ -670,7 +672,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
deploy: protectedProcedure
|
deploy: protectedProcedure
|
||||||
.input(apiFindOneApplication)
|
.input(apiDeployApplication)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
@@ -684,8 +686,8 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
const jobData: DeploymentJob = {
|
const jobData: DeploymentJob = {
|
||||||
applicationId: input.applicationId,
|
applicationId: input.applicationId,
|
||||||
titleLog: "Manual deployment",
|
titleLog: input.title || "Manual deployment",
|
||||||
descriptionLog: "",
|
descriptionLog: input.description || "",
|
||||||
type: "deploy",
|
type: "deploy",
|
||||||
applicationType: "application",
|
applicationType: "application",
|
||||||
server: !!application.serverId,
|
server: !!application.serverId,
|
||||||
|
|||||||
@@ -48,9 +48,11 @@ import { db } from "@/server/db";
|
|||||||
import {
|
import {
|
||||||
apiCreateCompose,
|
apiCreateCompose,
|
||||||
apiDeleteCompose,
|
apiDeleteCompose,
|
||||||
|
apiDeployCompose,
|
||||||
apiFetchServices,
|
apiFetchServices,
|
||||||
apiFindCompose,
|
apiFindCompose,
|
||||||
apiRandomizeCompose,
|
apiRandomizeCompose,
|
||||||
|
apiRedeployCompose,
|
||||||
apiUpdateCompose,
|
apiUpdateCompose,
|
||||||
compose as composeTable,
|
compose as composeTable,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
@@ -367,7 +369,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
deploy: protectedProcedure
|
deploy: protectedProcedure
|
||||||
.input(apiFindCompose)
|
.input(apiDeployCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
|
|
||||||
@@ -382,10 +384,10 @@ export const composeRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
const jobData: DeploymentJob = {
|
const jobData: DeploymentJob = {
|
||||||
composeId: input.composeId,
|
composeId: input.composeId,
|
||||||
titleLog: "Manual deployment",
|
titleLog: input.title || "Manual deployment",
|
||||||
type: "deploy",
|
type: "deploy",
|
||||||
applicationType: "compose",
|
applicationType: "compose",
|
||||||
descriptionLog: "",
|
descriptionLog: input.description || "",
|
||||||
server: !!compose.serverId,
|
server: !!compose.serverId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -404,7 +406,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
redeploy: protectedProcedure
|
redeploy: protectedProcedure
|
||||||
.input(apiFindCompose)
|
.input(apiRedeployCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (
|
||||||
@@ -418,10 +420,10 @@ export const composeRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
const jobData: DeploymentJob = {
|
const jobData: DeploymentJob = {
|
||||||
composeId: input.composeId,
|
composeId: input.composeId,
|
||||||
titleLog: "Rebuild deployment",
|
titleLog: input.title || "Rebuild deployment",
|
||||||
type: "redeploy",
|
type: "redeploy",
|
||||||
applicationType: "compose",
|
applicationType: "compose",
|
||||||
descriptionLog: "",
|
descriptionLog: input.description || "",
|
||||||
server: !!compose.serverId,
|
server: !!compose.serverId,
|
||||||
};
|
};
|
||||||
if (IS_CLOUD && compose.serverId) {
|
if (IS_CLOUD && compose.serverId) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
createDiscordNotification,
|
createDiscordNotification,
|
||||||
createEmailNotification,
|
createEmailNotification,
|
||||||
createGotifyNotification,
|
createGotifyNotification,
|
||||||
|
createNtfyNotification,
|
||||||
createSlackNotification,
|
createSlackNotification,
|
||||||
createTelegramNotification,
|
createTelegramNotification,
|
||||||
findNotificationById,
|
findNotificationById,
|
||||||
@@ -10,12 +11,14 @@ import {
|
|||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendNtfyNotification,
|
||||||
sendServerThresholdNotifications,
|
sendServerThresholdNotifications,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
updateDiscordNotification,
|
updateDiscordNotification,
|
||||||
updateEmailNotification,
|
updateEmailNotification,
|
||||||
updateGotifyNotification,
|
updateGotifyNotification,
|
||||||
|
updateNtfyNotification,
|
||||||
updateSlackNotification,
|
updateSlackNotification,
|
||||||
updateTelegramNotification,
|
updateTelegramNotification,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
@@ -33,17 +36,20 @@ import {
|
|||||||
apiCreateDiscord,
|
apiCreateDiscord,
|
||||||
apiCreateEmail,
|
apiCreateEmail,
|
||||||
apiCreateGotify,
|
apiCreateGotify,
|
||||||
|
apiCreateNtfy,
|
||||||
apiCreateSlack,
|
apiCreateSlack,
|
||||||
apiCreateTelegram,
|
apiCreateTelegram,
|
||||||
apiFindOneNotification,
|
apiFindOneNotification,
|
||||||
apiTestDiscordConnection,
|
apiTestDiscordConnection,
|
||||||
apiTestEmailConnection,
|
apiTestEmailConnection,
|
||||||
apiTestGotifyConnection,
|
apiTestGotifyConnection,
|
||||||
|
apiTestNtfyConnection,
|
||||||
apiTestSlackConnection,
|
apiTestSlackConnection,
|
||||||
apiTestTelegramConnection,
|
apiTestTelegramConnection,
|
||||||
apiUpdateDiscord,
|
apiUpdateDiscord,
|
||||||
apiUpdateEmail,
|
apiUpdateEmail,
|
||||||
apiUpdateGotify,
|
apiUpdateGotify,
|
||||||
|
apiUpdateNtfy,
|
||||||
apiUpdateSlack,
|
apiUpdateSlack,
|
||||||
apiUpdateTelegram,
|
apiUpdateTelegram,
|
||||||
notifications,
|
notifications,
|
||||||
@@ -321,6 +327,7 @@ export const notificationRouter = createTRPCRouter({
|
|||||||
discord: true,
|
discord: true,
|
||||||
email: true,
|
email: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
|
ntfy: true,
|
||||||
},
|
},
|
||||||
orderBy: desc(notifications.createdAt),
|
orderBy: desc(notifications.createdAt),
|
||||||
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
||||||
@@ -446,6 +453,64 @@ export const notificationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
createNtfy: adminProcedure
|
||||||
|
.input(apiCreateNtfy)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
|
return await createNtfyNotification(
|
||||||
|
input,
|
||||||
|
ctx.session.activeOrganizationId,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error creating the notification",
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
updateNtfy: adminProcedure
|
||||||
|
.input(apiUpdateNtfy)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
|
const notification = await findNotificationById(input.notificationId);
|
||||||
|
if (
|
||||||
|
IS_CLOUD &&
|
||||||
|
notification.organizationId !== ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to update this notification",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return await updateNtfyNotification({
|
||||||
|
...input,
|
||||||
|
organizationId: ctx.session.activeOrganizationId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
testNtfyConnection: adminProcedure
|
||||||
|
.input(apiTestNtfyConnection)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
try {
|
||||||
|
await sendNtfyNotification(
|
||||||
|
input,
|
||||||
|
"Test Notification",
|
||||||
|
"",
|
||||||
|
"view, visit Dokploy on Github, https://github.com/dokploy/dokploy, clear=true;",
|
||||||
|
"Hi, From Dokploy 👋",
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error testing the notification",
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
getEmailProviders: adminProcedure.query(async ({ ctx }) => {
|
getEmailProviders: adminProcedure.query(async ({ ctx }) => {
|
||||||
return await db.query.notifications.findMany({
|
return await db.query.notifications.findMany({
|
||||||
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
|||||||
@@ -328,6 +328,26 @@ export const apiFindOneApplication = createSchema
|
|||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
|
export const apiDeployApplication = createSchema
|
||||||
|
.pick({
|
||||||
|
applicationId: true,
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
applicationId: z.string().min(1),
|
||||||
|
title: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiRedeployApplication = createSchema
|
||||||
|
.pick({
|
||||||
|
applicationId: true,
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
applicationId: z.string().min(1),
|
||||||
|
title: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export const apiReloadApplication = createSchema
|
export const apiReloadApplication = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
appName: true,
|
appName: true,
|
||||||
|
|||||||
@@ -181,6 +181,18 @@ export const apiFindCompose = z.object({
|
|||||||
composeId: z.string().min(1),
|
composeId: z.string().min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const apiDeployCompose = z.object({
|
||||||
|
composeId: z.string().min(1),
|
||||||
|
title: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiRedeployCompose = z.object({
|
||||||
|
composeId: z.string().min(1),
|
||||||
|
title: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export const apiDeleteCompose = z.object({
|
export const apiDeleteCompose = z.object({
|
||||||
composeId: z.string().min(1),
|
composeId: z.string().min(1),
|
||||||
deleteVolumes: z.boolean(),
|
deleteVolumes: z.boolean(),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const notificationType = pgEnum("notificationType", [
|
|||||||
"discord",
|
"discord",
|
||||||
"email",
|
"email",
|
||||||
"gotify",
|
"gotify",
|
||||||
|
"ntfy",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const notifications = pgTable("notification", {
|
export const notifications = pgTable("notification", {
|
||||||
@@ -44,6 +45,9 @@ export const notifications = pgTable("notification", {
|
|||||||
gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
|
gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
|
ntfyId: text("ntfyId").references(() => ntfy.ntfyId, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
}),
|
||||||
organizationId: text("organizationId")
|
organizationId: text("organizationId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => organization.id, { onDelete: "cascade" }),
|
.references(() => organization.id, { onDelete: "cascade" }),
|
||||||
@@ -101,6 +105,17 @@ export const gotify = pgTable("gotify", {
|
|||||||
decoration: boolean("decoration"),
|
decoration: boolean("decoration"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const ntfy = pgTable("ntfy", {
|
||||||
|
ntfyId: text("ntfyId")
|
||||||
|
.notNull()
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => nanoid()),
|
||||||
|
serverUrl: text("serverUrl").notNull(),
|
||||||
|
topic: text("topic").notNull(),
|
||||||
|
accessToken: text("accessToken").notNull(),
|
||||||
|
priority: integer("priority").notNull().default(3),
|
||||||
|
});
|
||||||
|
|
||||||
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
||||||
slack: one(slack, {
|
slack: one(slack, {
|
||||||
fields: [notifications.slackId],
|
fields: [notifications.slackId],
|
||||||
@@ -122,6 +137,10 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
|
|||||||
fields: [notifications.gotifyId],
|
fields: [notifications.gotifyId],
|
||||||
references: [gotify.gotifyId],
|
references: [gotify.gotifyId],
|
||||||
}),
|
}),
|
||||||
|
ntfy: one(ntfy, {
|
||||||
|
fields: [notifications.ntfyId],
|
||||||
|
references: [ntfy.ntfyId],
|
||||||
|
}),
|
||||||
organization: one(organization, {
|
organization: one(organization, {
|
||||||
fields: [notifications.organizationId],
|
fields: [notifications.organizationId],
|
||||||
references: [organization.id],
|
references: [organization.id],
|
||||||
@@ -284,6 +303,36 @@ export const apiTestGotifyConnection = apiCreateGotify
|
|||||||
decoration: z.boolean().optional(),
|
decoration: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const apiCreateNtfy = notificationsSchema
|
||||||
|
.pick({
|
||||||
|
appBuildError: true,
|
||||||
|
databaseBackup: true,
|
||||||
|
dokployRestart: true,
|
||||||
|
name: true,
|
||||||
|
appDeploy: true,
|
||||||
|
dockerCleanup: true,
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
serverUrl: z.string().min(1),
|
||||||
|
topic: z.string().min(1),
|
||||||
|
accessToken: z.string().min(1),
|
||||||
|
priority: z.number().min(1),
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export const apiUpdateNtfy = apiCreateNtfy.partial().extend({
|
||||||
|
notificationId: z.string().min(1),
|
||||||
|
ntfyId: z.string().min(1),
|
||||||
|
organizationId: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiTestNtfyConnection = apiCreateNtfy.pick({
|
||||||
|
serverUrl: true,
|
||||||
|
topic: true,
|
||||||
|
accessToken: true,
|
||||||
|
priority: true,
|
||||||
|
});
|
||||||
|
|
||||||
export const apiFindOneNotification = notificationsSchema
|
export const apiFindOneNotification = notificationsSchema
|
||||||
.pick({
|
.pick({
|
||||||
notificationId: true,
|
notificationId: true,
|
||||||
@@ -303,7 +352,9 @@ export const apiSendTest = notificationsSchema
|
|||||||
password: z.string(),
|
password: z.string(),
|
||||||
toAddresses: z.array(z.string()),
|
toAddresses: z.array(z.string()),
|
||||||
serverUrl: z.string(),
|
serverUrl: z.string(),
|
||||||
|
topic: z.string(),
|
||||||
appToken: z.string(),
|
appToken: z.string(),
|
||||||
|
accessToken: z.string(),
|
||||||
priority: z.number(),
|
priority: z.number(),
|
||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|||||||
@@ -3,17 +3,20 @@ import {
|
|||||||
type apiCreateDiscord,
|
type apiCreateDiscord,
|
||||||
type apiCreateEmail,
|
type apiCreateEmail,
|
||||||
type apiCreateGotify,
|
type apiCreateGotify,
|
||||||
|
type apiCreateNtfy,
|
||||||
type apiCreateSlack,
|
type apiCreateSlack,
|
||||||
type apiCreateTelegram,
|
type apiCreateTelegram,
|
||||||
type apiUpdateDiscord,
|
type apiUpdateDiscord,
|
||||||
type apiUpdateEmail,
|
type apiUpdateEmail,
|
||||||
type apiUpdateGotify,
|
type apiUpdateGotify,
|
||||||
|
type apiUpdateNtfy,
|
||||||
type apiUpdateSlack,
|
type apiUpdateSlack,
|
||||||
type apiUpdateTelegram,
|
type apiUpdateTelegram,
|
||||||
discord,
|
discord,
|
||||||
email,
|
email,
|
||||||
gotify,
|
gotify,
|
||||||
notifications,
|
notifications,
|
||||||
|
ntfy,
|
||||||
slack,
|
slack,
|
||||||
telegram,
|
telegram,
|
||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
@@ -482,6 +485,96 @@ export const updateGotifyNotification = async (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createNtfyNotification = async (
|
||||||
|
input: typeof apiCreateNtfy._type,
|
||||||
|
organizationId: string,
|
||||||
|
) => {
|
||||||
|
await db.transaction(async (tx) => {
|
||||||
|
const newNtfy = await tx
|
||||||
|
.insert(ntfy)
|
||||||
|
.values({
|
||||||
|
serverUrl: input.serverUrl,
|
||||||
|
topic: input.topic,
|
||||||
|
accessToken: input.accessToken,
|
||||||
|
priority: input.priority,
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
.then((value) => value[0]);
|
||||||
|
|
||||||
|
if (!newNtfy) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error input: Inserting ntfy",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDestination = await tx
|
||||||
|
.insert(notifications)
|
||||||
|
.values({
|
||||||
|
ntfyId: newNtfy.ntfyId,
|
||||||
|
name: input.name,
|
||||||
|
appDeploy: input.appDeploy,
|
||||||
|
appBuildError: input.appBuildError,
|
||||||
|
databaseBackup: input.databaseBackup,
|
||||||
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
|
notificationType: "ntfy",
|
||||||
|
organizationId: organizationId,
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
.then((value) => value[0]);
|
||||||
|
|
||||||
|
if (!newDestination) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error input: Inserting notification",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDestination;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateNtfyNotification = async (
|
||||||
|
input: typeof apiUpdateNtfy._type,
|
||||||
|
) => {
|
||||||
|
await db.transaction(async (tx) => {
|
||||||
|
const newDestination = await tx
|
||||||
|
.update(notifications)
|
||||||
|
.set({
|
||||||
|
name: input.name,
|
||||||
|
appDeploy: input.appDeploy,
|
||||||
|
appBuildError: input.appBuildError,
|
||||||
|
databaseBackup: input.databaseBackup,
|
||||||
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
|
organizationId: input.organizationId,
|
||||||
|
})
|
||||||
|
.where(eq(notifications.notificationId, input.notificationId))
|
||||||
|
.returning()
|
||||||
|
.then((value) => value[0]);
|
||||||
|
|
||||||
|
if (!newDestination) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error Updating notification",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx
|
||||||
|
.update(ntfy)
|
||||||
|
.set({
|
||||||
|
serverUrl: input.serverUrl,
|
||||||
|
topic: input.topic,
|
||||||
|
accessToken: input.accessToken,
|
||||||
|
priority: input.priority,
|
||||||
|
})
|
||||||
|
.where(eq(ntfy.ntfyId, input.ntfyId));
|
||||||
|
|
||||||
|
return newDestination;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const findNotificationById = async (notificationId: string) => {
|
export const findNotificationById = async (notificationId: string) => {
|
||||||
const notification = await db.query.notifications.findFirst({
|
const notification = await db.query.notifications.findFirst({
|
||||||
where: eq(notifications.notificationId, notificationId),
|
where: eq(notifications.notificationId, notificationId),
|
||||||
@@ -491,6 +584,7 @@ export const findNotificationById = async (notificationId: string) => {
|
|||||||
discord: true,
|
discord: true,
|
||||||
email: true,
|
email: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
|
ntfy: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!notification) {
|
if (!notification) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -42,11 +43,12 @@ export const sendBuildErrorNotifications = async ({
|
|||||||
telegram: true,
|
telegram: true,
|
||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
|
ntfy: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify } = notification;
|
const { email, discord, telegram, slack, gotify, ntfy } = notification;
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
BuildFailedEmail({
|
BuildFailedEmail({
|
||||||
@@ -132,6 +134,20 @@ export const sendBuildErrorNotifications = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ntfy) {
|
||||||
|
await sendNtfyNotification(
|
||||||
|
ntfy,
|
||||||
|
"Build Failed",
|
||||||
|
"warning",
|
||||||
|
`view, Build details, ${buildLink}, clear=true;`,
|
||||||
|
`🛠️Project: ${projectName}\n` +
|
||||||
|
`⚙️Application: ${applicationName}\n` +
|
||||||
|
`❔Type: ${applicationType}\n` +
|
||||||
|
`🕒Date: ${date.toLocaleString()}\n` +
|
||||||
|
`⚠️Error:\n${errorMessage}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (telegram) {
|
if (telegram) {
|
||||||
const inlineButton = [
|
const inlineButton = [
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -43,11 +44,12 @@ export const sendBuildSuccessNotifications = async ({
|
|||||||
telegram: true,
|
telegram: true,
|
||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
|
ntfy: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify } = notification;
|
const { email, discord, telegram, slack, gotify, ntfy } = notification;
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -126,6 +128,19 @@ export const sendBuildSuccessNotifications = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ntfy) {
|
||||||
|
await sendNtfyNotification(
|
||||||
|
ntfy,
|
||||||
|
"Build Success",
|
||||||
|
"white_check_mark",
|
||||||
|
`view, Build details, ${buildLink}, clear=true;`,
|
||||||
|
`🛠Project: ${projectName}\n` +
|
||||||
|
`⚙️Application: ${applicationName}\n` +
|
||||||
|
`❔Type: ${applicationType}\n` +
|
||||||
|
`🕒Date: ${date.toLocaleString()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (telegram) {
|
if (telegram) {
|
||||||
const chunkArray = <T>(array: T[], chunkSize: number): T[][] =>
|
const chunkArray = <T>(array: T[], chunkSize: number): T[][] =>
|
||||||
Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) =>
|
Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) =>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -42,11 +43,12 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
telegram: true,
|
telegram: true,
|
||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
|
ntfy: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify } = notification;
|
const { email, discord, telegram, slack, gotify, ntfy } = notification;
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -149,6 +151,21 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ntfy) {
|
||||||
|
await sendNtfyNotification(
|
||||||
|
ntfy,
|
||||||
|
`Database Backup ${type === "success" ? "Successful" : "Failed"}`,
|
||||||
|
`${type === "success" ? "white_check_mark" : "x"}`,
|
||||||
|
"",
|
||||||
|
`🛠Project: ${projectName}\n` +
|
||||||
|
`⚙️Application: ${applicationName}\n` +
|
||||||
|
`❔Type: ${databaseType}\n` +
|
||||||
|
`📂Database Name: ${databaseName}` +
|
||||||
|
`🕒Date: ${date.toLocaleString()}\n` +
|
||||||
|
`${type === "error" && errorMessage ? `❌Error:\n${errorMessage}` : ""}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (telegram) {
|
if (telegram) {
|
||||||
const isError = type === "error" && errorMessage;
|
const isError = type === "error" && errorMessage;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -29,11 +30,12 @@ export const sendDockerCleanupNotifications = async (
|
|||||||
telegram: true,
|
telegram: true,
|
||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
|
ntfy: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify } = notification;
|
const { email, discord, telegram, slack, gotify, ntfy } = notification;
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -93,6 +95,16 @@ export const sendDockerCleanupNotifications = async (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ntfy) {
|
||||||
|
await sendNtfyNotification(
|
||||||
|
ntfy,
|
||||||
|
"Docker Cleanup",
|
||||||
|
"white_check_mark",
|
||||||
|
"",
|
||||||
|
`🕒Date: ${date.toLocaleString()}\n` + `📜Message:\n${message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (telegram) {
|
if (telegram) {
|
||||||
await sendTelegramNotification(
|
await sendTelegramNotification(
|
||||||
telegram,
|
telegram,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -23,11 +24,12 @@ export const sendDokployRestartNotifications = async () => {
|
|||||||
telegram: true,
|
telegram: true,
|
||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
|
ntfy: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify } = notification;
|
const { email, discord, telegram, slack, gotify, ntfy } = notification;
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -85,6 +87,20 @@ export const sendDokployRestartNotifications = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ntfy) {
|
||||||
|
try {
|
||||||
|
await sendNtfyNotification(
|
||||||
|
ntfy,
|
||||||
|
"Dokploy Server Restarted",
|
||||||
|
"white_check_mark",
|
||||||
|
"",
|
||||||
|
`🕒Date: ${date.toLocaleString()}`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (telegram) {
|
if (telegram) {
|
||||||
try {
|
try {
|
||||||
await sendTelegramNotification(
|
await sendTelegramNotification(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type {
|
|||||||
discord,
|
discord,
|
||||||
email,
|
email,
|
||||||
gotify,
|
gotify,
|
||||||
|
ntfy,
|
||||||
slack,
|
slack,
|
||||||
telegram,
|
telegram,
|
||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
@@ -126,3 +127,27 @@ export const sendGotifyNotification = async (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sendNtfyNotification = async (
|
||||||
|
connection: typeof ntfy.$inferInsert,
|
||||||
|
title: string,
|
||||||
|
tags: string,
|
||||||
|
actions: string,
|
||||||
|
message: string,
|
||||||
|
) => {
|
||||||
|
const response = await fetch(`${connection.serverUrl}/${connection.topic}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${connection.accessToken}`,
|
||||||
|
"X-Priority": connection.priority?.toString() || "3",
|
||||||
|
"X-Title": title,
|
||||||
|
"X-Tags": tags,
|
||||||
|
"X-Actions": actions,
|
||||||
|
},
|
||||||
|
body: message,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to send ntfy notification: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user