mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f6f872536 | ||
|
|
cb03b153ac | ||
|
|
e5d7a0cb10 | ||
|
|
bf65bc9462 | ||
|
|
b48b9765cd | ||
|
|
7cce02f74d | ||
|
|
3dc3672406 | ||
|
|
bbfe095045 | ||
|
|
65c1001751 | ||
|
|
5212bde021 | ||
|
|
9059f42b03 | ||
|
|
3b9f5d6f5c | ||
|
|
21dee4abac |
@@ -74,7 +74,7 @@ export function generateMetadata({
|
|||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: "summary_large_image",
|
card: "summary_large_image",
|
||||||
creator: "@siumauricio",
|
creator: "@getdokploy",
|
||||||
title: page.data.title,
|
title: page.data.title,
|
||||||
description: page.data.description,
|
description: page.data.description,
|
||||||
images: [
|
images: [
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ Configure the source of your code, the way your application is built, and also m
|
|||||||
|
|
||||||
If you need to assign environment variables to your application, you can do so here.
|
If you need to assign environment variables to your application, you can do so here.
|
||||||
|
|
||||||
|
In case you need to use a multiline variable, you can wrap it in double quotes just like this `'"here_is_my_private_key"'`.
|
||||||
|
|
||||||
## Monitoring
|
## Monitoring
|
||||||
|
|
||||||
Four graphs will be displayed for the use of memory, CPU, disk, and network. Note that the information is only updated if you are viewing the current page, otherwise it will not be updated.
|
Four graphs will be displayed for the use of memory, CPU, disk, and network. Note that the information is only updated if you are viewing the current page, otherwise it will not be updated.
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ Actions like deploying, updating, and deleting your database, and stopping it.
|
|||||||
|
|
||||||
If you need to assign environment variables to your application, you can do so here.
|
If you need to assign environment variables to your application, you can do so here.
|
||||||
|
|
||||||
|
In case you need to use a multiline variable, you can wrap it in double quotes just like this `'"here_is_my_private_key"'`.
|
||||||
|
|
||||||
## Monitoring
|
## Monitoring
|
||||||
|
|
||||||
Four graphs will be displayed for the use of memory, CPU, disk, and network. Note that the information is only updated if you are viewing the current page, otherwise it will not be updated.
|
Four graphs will be displayed for the use of memory, CPU, disk, and network. Note that the information is only updated if you are viewing the current page, otherwise it will not be updated.
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
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";
|
||||||
@@ -36,6 +45,36 @@ const AddRedirectchema = z.object({
|
|||||||
|
|
||||||
type AddRedirect = z.infer<typeof AddRedirectchema>;
|
type AddRedirect = z.infer<typeof AddRedirectchema>;
|
||||||
|
|
||||||
|
// Default presets
|
||||||
|
const redirectPresets = [
|
||||||
|
// {
|
||||||
|
// label: "Allow www & non-www.",
|
||||||
|
// redirect: {
|
||||||
|
// regex: "",
|
||||||
|
// permanent: false,
|
||||||
|
// replacement: "",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
id: "to-www",
|
||||||
|
label: "Redirect to www",
|
||||||
|
redirect: {
|
||||||
|
regex: "^https?://(?:www.)?(.+)",
|
||||||
|
permanent: true,
|
||||||
|
replacement: "https://www.$${1}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "to-non-www",
|
||||||
|
label: "Redirect to non-www",
|
||||||
|
redirect: {
|
||||||
|
regex: "^https?://www.(.+)",
|
||||||
|
permanent: true,
|
||||||
|
replacement: "https://$${1}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@@ -43,9 +82,10 @@ interface Props {
|
|||||||
|
|
||||||
export const AddRedirect = ({
|
export const AddRedirect = ({
|
||||||
applicationId,
|
applicationId,
|
||||||
children = <PlusIcon className="h-4 w-4" />,
|
children = <PlusIcon className="w-4 h-4" />,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [presetSelected, setPresetSelected] = useState("");
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const { mutateAsync, isLoading, error, isError } =
|
const { mutateAsync, isLoading, error, isError } =
|
||||||
@@ -81,19 +121,36 @@ export const AddRedirect = ({
|
|||||||
await utils.application.readTraefikConfig.invalidate({
|
await utils.application.readTraefikConfig.invalidate({
|
||||||
applicationId,
|
applicationId,
|
||||||
});
|
});
|
||||||
setIsOpen(false);
|
onDialogToggle(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Error to create the redirect");
|
toast.error("Error to create the redirect");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDialogToggle = (open: boolean) => {
|
||||||
|
setIsOpen(open);
|
||||||
|
// commented for the moment because not reseting the form if accidentally closed the dialog can be considered as a feature instead of a bug
|
||||||
|
// setPresetSelected("");
|
||||||
|
// form.reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPresetSelect = (presetId: string) => {
|
||||||
|
const redirectPreset = redirectPresets.find(
|
||||||
|
(preset) => preset.id === presetId,
|
||||||
|
)?.redirect;
|
||||||
|
if (!redirectPreset) return;
|
||||||
|
const { regex, permanent, replacement } = redirectPreset;
|
||||||
|
form.reset({ regex, permanent, replacement }, { keepDefaultValues: true });
|
||||||
|
setPresetSelected(presetId);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={onDialogToggle}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button>{children}</Button>
|
<Button>{children}</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Redirects</DialogTitle>
|
<DialogTitle>Redirects</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -102,6 +159,24 @@ export const AddRedirect = ({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
|
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<Label>Presets</Label>
|
||||||
|
<Select onValueChange={onPresetSelect} value={presetSelected}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="No preset selected" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{redirectPresets.map((preset) => (
|
||||||
|
<SelectItem key={preset.label} value={preset.id}>
|
||||||
|
{preset.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
id="hook-form-add-redirect"
|
id="hook-form-add-redirect"
|
||||||
@@ -142,7 +217,7 @@ export const AddRedirect = ({
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="permanent"
|
name="permanent"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel>Permanent</FormLabel>
|
<FormLabel>Permanent</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export const AddDomain = ({
|
|||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
{children}
|
{children}
|
||||||
</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">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Domain</DialogTitle>
|
<DialogTitle>Domain</DialogTitle>
|
||||||
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
||||||
@@ -241,6 +241,29 @@ export const AddDomain = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="https"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>HTTPS</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Automatically provision SSL Certificate.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
{form.getValues().https && (
|
{form.getValues().https && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -270,28 +293,6 @@ export const AddDomain = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="https"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel>HTTPS</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Automatically provision SSL Certificate.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export const AddDomainCompose = ({
|
|||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
{children}
|
{children}
|
||||||
</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">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Domain</DialogTitle>
|
<DialogTitle>Domain</DialogTitle>
|
||||||
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
||||||
@@ -190,7 +190,7 @@ export const AddDomainCompose = ({
|
|||||||
{errorServices?.message}
|
{errorServices?.message}
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-row gap-4 w-full items-end">
|
<div className="flex flex-row items-end w-full gap-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="serviceName"
|
name="serviceName"
|
||||||
@@ -377,6 +377,29 @@ export const AddDomainCompose = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="https"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>HTTPS</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Automatically provision SSL Certificate.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
{https && (
|
{https && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -406,28 +429,6 @@ export const AddDomainCompose = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="https"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel>HTTPS</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Automatically provision SSL Certificate.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.9.0",
|
"version": "v0.9.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { IncomingMessage } from "node:http";
|
import type { IncomingMessage } from "node:http";
|
||||||
import { TimeSpan } from "lucia";
|
import { TimeSpan } from "lucia";
|
||||||
import { Lucia } from "lucia/dist/core.js";
|
import { Lucia } from "lucia/dist/core.js";
|
||||||
|
import { findAdminByAuthId } from "../api/services/admin";
|
||||||
|
import { findUserByAuthId } from "../api/services/user";
|
||||||
import { type ReturnValidateToken, adapter } from "./auth";
|
import { type ReturnValidateToken, adapter } from "./auth";
|
||||||
|
|
||||||
export const luciaToken = new Lucia(adapter, {
|
export const luciaToken = new Lucia(adapter, {
|
||||||
@@ -31,6 +33,16 @@ export const validateBearerToken = async (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
const result = await luciaToken.validateSession(sessionId);
|
const result = await luciaToken.validateSession(sessionId);
|
||||||
|
|
||||||
|
if (result.user) {
|
||||||
|
if (result.user?.rol === "admin") {
|
||||||
|
const admin = await findAdminByAuthId(result.user.id);
|
||||||
|
result.user.adminId = admin.adminId;
|
||||||
|
} else if (result.user?.rol === "user") {
|
||||||
|
const userResult = await findUserByAuthId(result.user.id);
|
||||||
|
result.user.adminId = userResult.adminId;
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
session: result.session,
|
session: result.session,
|
||||||
...((result.user && {
|
...((result.user && {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { WriteStream } from "node:fs";
|
import { type WriteStream, existsSync, mkdirSync } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { buildStatic, getStaticCommand } from "@/server/utils/builders/static";
|
import { buildStatic, getStaticCommand } from "@/server/utils/builders/static";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
@@ -42,7 +42,6 @@ export const buildNixpacks = async (
|
|||||||
and copy the artifacts on the host filesystem.
|
and copy the artifacts on the host filesystem.
|
||||||
Then, remove the container and create a static build.
|
Then, remove the container and create a static build.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (publishDirectory) {
|
if (publishDirectory) {
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
"docker",
|
"docker",
|
||||||
@@ -50,12 +49,22 @@ export const buildNixpacks = async (
|
|||||||
writeToStream,
|
writeToStream,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const localPath = path.join(buildAppDirectory, publishDirectory);
|
||||||
|
|
||||||
|
if (!existsSync(path.dirname(localPath))) {
|
||||||
|
mkdirSync(path.dirname(localPath), { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.docker.com/reference/cli/docker/container/cp/
|
||||||
|
const isDirectory =
|
||||||
|
publishDirectory.endsWith("/") || !path.extname(publishDirectory);
|
||||||
|
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
"docker",
|
"docker",
|
||||||
[
|
[
|
||||||
"cp",
|
"cp",
|
||||||
`${buildContainerId}:/app/${publishDirectory}`,
|
`${buildContainerId}:/app/${publishDirectory}${isDirectory ? "/." : ""}`,
|
||||||
path.join(buildAppDirectory, publishDirectory),
|
localPath,
|
||||||
],
|
],
|
||||||
writeToStream,
|
writeToStream,
|
||||||
);
|
);
|
||||||
@@ -108,9 +117,14 @@ echo "✅ Nixpacks build completed." >> ${logPath};
|
|||||||
Then, remove the container and create a static build.
|
Then, remove the container and create a static build.
|
||||||
*/
|
*/
|
||||||
if (publishDirectory) {
|
if (publishDirectory) {
|
||||||
|
const localPath = path.join(buildAppDirectory, publishDirectory);
|
||||||
|
const isDirectory =
|
||||||
|
publishDirectory.endsWith("/") || !path.extname(publishDirectory);
|
||||||
|
|
||||||
bashCommand += `
|
bashCommand += `
|
||||||
docker create --name ${buildContainerId} ${appName}
|
docker create --name ${buildContainerId} ${appName}
|
||||||
docker cp ${buildContainerId}:/app/${publishDirectory} ${path.join(buildAppDirectory, publishDirectory)} >> ${logPath} 2>> ${logPath} || {
|
mkdir -p ${localPath}
|
||||||
|
docker cp ${buildContainerId}:/app/${publishDirectory}${isDirectory ? "/." : ""} ${path.join(buildAppDirectory, publishDirectory)} >> ${logPath} 2>> ${logPath} || {
|
||||||
docker rm ${buildContainerId}
|
docker rm ${buildContainerId}
|
||||||
echo "❌ Copying ${publishDirectory} to ${path.join(buildAppDirectory, publishDirectory)} failed" >> ${logPath};
|
echo "❌ Copying ${publishDirectory} to ${path.join(buildAppDirectory, publishDirectory)} failed" >> ${logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function Footer() {
|
|||||||
<div className="flex flex-col items-center border-t border-slate-400/10 py-10 sm:flex-row-reverse sm:justify-between">
|
<div className="flex flex-col items-center border-t border-slate-400/10 py-10 sm:flex-row-reverse sm:justify-between">
|
||||||
<div className="flex gap-x-6">
|
<div className="flex gap-x-6">
|
||||||
<Link
|
<Link
|
||||||
href="https://twitter.com/Siumauricio"
|
href="https://x.com/getdokploy"
|
||||||
className="group"
|
className="group"
|
||||||
aria-label="Dokploy on Twitter"
|
aria-label="Dokploy on Twitter"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user