mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-02 18:22:16 +00:00
Merge branch 'canary' into 216-domains-for-services-created-via-template
This commit is contained in:
@@ -46,6 +46,7 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
|
||||
<div className="flex flex-col pt-2 relative">
|
||||
<div className="flex flex-col gap-6 max-h-[35rem] min-h-[10rem] overflow-y-auto">
|
||||
<CodeEditor
|
||||
lineWrapping
|
||||
value={data || "Empty"}
|
||||
disabled
|
||||
className="font-mono"
|
||||
|
||||
+1
@@ -144,6 +144,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
|
||||
<FormLabel>Traefik config</FormLabel>
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
lineWrapping
|
||||
wrapperClassName="h-[35rem] font-mono"
|
||||
placeholder={`http:
|
||||
routers:
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
@@ -23,6 +24,7 @@ enum BuildType {
|
||||
heroku_buildpacks = "heroku_buildpacks",
|
||||
paketo_buildpacks = "paketo_buildpacks",
|
||||
nixpacks = "nixpacks",
|
||||
static = "static",
|
||||
}
|
||||
|
||||
const mySchema = z.discriminatedUnion("buildType", [
|
||||
@@ -34,6 +36,7 @@ const mySchema = z.discriminatedUnion("buildType", [
|
||||
invalid_type_error: "Dockerfile path is required",
|
||||
})
|
||||
.min(1, "Dockerfile required"),
|
||||
dockerContextPath: z.string().nullable().default(""),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("heroku_buildpacks"),
|
||||
@@ -43,6 +46,10 @@ const mySchema = z.discriminatedUnion("buildType", [
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("nixpacks"),
|
||||
publishDirectory: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("static"),
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -73,17 +80,18 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
const buildType = form.watch("buildType");
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
// TODO: refactor this
|
||||
if (data.buildType === "dockerfile") {
|
||||
form.reset({
|
||||
buildType: data.buildType,
|
||||
...(data.buildType && {
|
||||
dockerfile: data.dockerfile || "",
|
||||
dockerContextPath: data.dockerContextPath || "",
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
buildType: data.buildType,
|
||||
publishDirectory: data.publishDirectory || undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -93,7 +101,11 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
await mutateAsync({
|
||||
applicationId,
|
||||
buildType: data.buildType,
|
||||
publishDirectory:
|
||||
data.buildType === "nixpacks" ? data.publishDirectory : null,
|
||||
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
|
||||
dockerContextPath:
|
||||
data.buildType === "dockerfile" ? data.dockerContextPath : null,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Build type saved");
|
||||
@@ -171,6 +183,12 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
Paketo Buildpacks
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="static" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">Static</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -179,16 +197,71 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
}}
|
||||
/>
|
||||
{buildType === "dockerfile" && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dockerfile"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Docker File</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={"Path of your docker file"}
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dockerContextPath"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Docker Context Path</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={
|
||||
"Path of your docker context default: ."
|
||||
}
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{buildType === "nixpacks" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dockerfile"
|
||||
name="publishDirectory"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Docker File</FormLabel>
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Publish Directory</FormLabel>
|
||||
<FormDescription>
|
||||
Allows you to serve a single directory via NGINX after
|
||||
the build phase. Useful if the final build assets
|
||||
should be served as a static site.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={"Path of your docker file"}
|
||||
placeholder={"Publish Directory"}
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
/>
|
||||
|
||||
@@ -53,7 +53,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
|
||||
<div className="flex flex-row items-center gap-2 flex-wrap">
|
||||
<span>Webhook URL: </span>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<span className="text-muted-foreground">
|
||||
<span className="break-all text-muted-foreground">
|
||||
{`${url}/api/deploy/${data?.refreshToken}`}
|
||||
</span>
|
||||
<RefreshToken applicationId={applicationId} />
|
||||
@@ -72,7 +72,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
|
||||
{deployments?.map((deployment) => (
|
||||
<div
|
||||
key={deployment.deploymentId}
|
||||
className="flex items-center justify-between rounded-lg border p-4"
|
||||
className="flex items-center justify-between rounded-lg border p-4 gap-2"
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="flex items-center gap-4 font-medium capitalize text-foreground">
|
||||
@@ -87,7 +87,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
|
||||
{deployment.title}
|
||||
</span>
|
||||
{deployment.description && (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="break-all text-sm text-muted-foreground">
|
||||
{deployment.description}
|
||||
</span>
|
||||
)}
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Password" {...field} />
|
||||
<Input placeholder="Password" {...field} type="password" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -104,6 +104,7 @@ export const ShowTraefikFile = ({ path }: Props) => {
|
||||
</FormDescription>
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
lineWrapping
|
||||
wrapperClassName="h-[35rem] font-mono"
|
||||
placeholder={`http:
|
||||
routers:
|
||||
|
||||
@@ -55,6 +55,7 @@ interface Props {
|
||||
|
||||
export const AddTemplate = ({ projectId }: Props) => {
|
||||
const [query, setQuery] = useState("");
|
||||
const [open, setOpen] = useState(false);
|
||||
const { data } = api.compose.templates.useQuery();
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||
const { data: tags, isLoading: isLoadingTags } =
|
||||
@@ -75,14 +76,14 @@ export const AddTemplate = ({ projectId }: Props) => {
|
||||
}) || [];
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger className="w-full">
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer space-x-3"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
<PuzzleIcon className="size-4 text-muted-foreground" />
|
||||
<span>Templates</span>
|
||||
<span>Template</span>
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl p-0">
|
||||
@@ -283,6 +284,7 @@ export const AddTemplate = ({ projectId }: Props) => {
|
||||
utils.project.one.invalidate({
|
||||
projectId,
|
||||
});
|
||||
setOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error(
|
||||
|
||||
@@ -9,36 +9,11 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { format } from "date-fns";
|
||||
import { BadgeCheck } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { RemoveGithubApp } from "./remove-github-app";
|
||||
export const generateName = () => {
|
||||
const n1 = ["Blue", "Green", "Red", "Orange", "Violet", "Indigo", "Yellow"];
|
||||
const n2 = [
|
||||
"One",
|
||||
"Two",
|
||||
"Three",
|
||||
"Four",
|
||||
"Five",
|
||||
"Six",
|
||||
"Seven",
|
||||
"Eight",
|
||||
"Nine",
|
||||
"Zero",
|
||||
];
|
||||
return `Dokploy-${n1[Math.round(Math.random() * (n1.length - 1))]}-${
|
||||
n2[Math.round(Math.random() * (n2.length - 1))]
|
||||
}`;
|
||||
};
|
||||
function slugify(text: string) {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[\s\^&*()+=!]+/g, "-")
|
||||
.replace(/[\$.,*+~()'"!:@^&]+/g, "")
|
||||
.replace(/-+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
export const GithubSetup = () => {
|
||||
const [isOrganization, setIsOrganization] = useState(false);
|
||||
@@ -52,10 +27,9 @@ export const GithubSetup = () => {
|
||||
const manifest = JSON.stringify(
|
||||
{
|
||||
redirect_url: `${origin}/api/redirect?authId=${data?.authId}`,
|
||||
name: generateName(),
|
||||
name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}`,
|
||||
url: origin,
|
||||
hook_attributes: {
|
||||
// JUST FOR TESTING
|
||||
url: `${url}/api/deploy/github`,
|
||||
// url: `${origin}/api/webhook`, // Aquí especificas la URL del endpoint de tu webhook
|
||||
},
|
||||
@@ -95,8 +69,8 @@ export const GithubSetup = () => {
|
||||
</div>
|
||||
<div className="flex items-end gap-4 flex-wrap">
|
||||
<RemoveGithubApp />
|
||||
{/* <Link
|
||||
href={`https://github.com/settings/apps/${data?.githubAppName}`}
|
||||
<Link
|
||||
href={`${data?.githubAppName}`}
|
||||
target="_blank"
|
||||
className={buttonVariants({
|
||||
className: "w-fit",
|
||||
@@ -104,7 +78,7 @@ export const GithubSetup = () => {
|
||||
})}
|
||||
>
|
||||
<span className="text-sm">Manage Github App</span>
|
||||
</Link> */}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -119,9 +93,9 @@ export const GithubSetup = () => {
|
||||
|
||||
<div className="flex flex-row gap-4">
|
||||
<Link
|
||||
href={`https://github.com/apps/${slugify(
|
||||
data.githubAppName,
|
||||
)}/installations/new?state=gh_setup:${data?.authId}`}
|
||||
href={`${
|
||||
data.githubAppName
|
||||
}/installations/new?state=gh_setup:${data?.authId}`}
|
||||
className={buttonVariants({ className: "w-fit" })}
|
||||
>
|
||||
Install Github App
|
||||
|
||||
@@ -22,7 +22,7 @@ export const ShowDestinations = () => {
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">SSH Keys</CardTitle>
|
||||
<CardDescription>
|
||||
Use SSH to beeing able cloning from private repositories.
|
||||
Use SSH to be able to clone from private repositories.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 pt-4">
|
||||
|
||||
@@ -39,6 +39,7 @@ const addPermissions = z.object({
|
||||
canAccessToTraefikFiles: z.boolean().optional().default(false),
|
||||
canAccessToDocker: z.boolean().optional().default(false),
|
||||
canAccessToAPI: z.boolean().optional().default(false),
|
||||
canAccessToSSHKeys: z.boolean().optional().default(false),
|
||||
});
|
||||
|
||||
type AddPermissions = z.infer<typeof addPermissions>;
|
||||
@@ -82,6 +83,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
|
||||
canAccessToDocker: data.canAccessToDocker,
|
||||
canAccessToAPI: data.canAccessToAPI,
|
||||
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||
});
|
||||
}
|
||||
}, [form, form.formState.isSubmitSuccessful, form.reset, data]);
|
||||
@@ -98,6 +100,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
accesedServices: data.accesedServices || [],
|
||||
canAccessToDocker: data.canAccessToDocker,
|
||||
canAccessToAPI: data.canAccessToAPI,
|
||||
canAccessToSSHKeys: data.canAccessToSSHKeys,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Permissions updated");
|
||||
@@ -270,6 +273,26 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="canAccessToSSHKeys"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Access to SSH Keys</FormLabel>
|
||||
<FormDescription>
|
||||
Allow to users to access to the SSH Keys section
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="accesedProjects"
|
||||
|
||||
@@ -106,6 +106,7 @@ export const ShowMainTraefikConfig = ({ children }: Props) => {
|
||||
<FormLabel>Traefik config</FormLabel>
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
lineWrapping
|
||||
wrapperClassName="h-[35rem] font-mono"
|
||||
placeholder={`providers:
|
||||
docker:
|
||||
|
||||
@@ -109,6 +109,7 @@ export const ShowServerTraefikConfig = ({ children }: Props) => {
|
||||
<FormLabel>Traefik config</FormLabel>
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
lineWrapping
|
||||
wrapperClassName="h-[35rem] font-mono"
|
||||
placeholder={`http:
|
||||
routers:
|
||||
|
||||
@@ -79,6 +79,16 @@ export const SettingsLayout = ({ children }: Props) => {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(user?.canAccessToSSHKeys
|
||||
? [
|
||||
{
|
||||
title: "SSH Keys",
|
||||
label: "",
|
||||
icon: KeyRound,
|
||||
href: "/dashboard/settings/ssh-keys",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { json } from "@codemirror/lang-json";
|
||||
import { yaml } from "@codemirror/lang-yaml";
|
||||
import { StreamLanguage } from "@codemirror/language";
|
||||
import { properties } from "@codemirror/legacy-modes/mode/properties";
|
||||
import { EditorView } from "@codemirror/view";
|
||||
import { githubDark, githubLight } from "@uiw/codemirror-theme-github";
|
||||
import CodeMirror, { type ReactCodeMirrorProps } from "@uiw/react-codemirror";
|
||||
import { useTheme } from "next-themes";
|
||||
@@ -10,6 +11,7 @@ interface Props extends ReactCodeMirrorProps {
|
||||
wrapperClassName?: string;
|
||||
disabled?: boolean;
|
||||
language?: "yaml" | "json" | "properties";
|
||||
lineWrapping?: boolean;
|
||||
}
|
||||
|
||||
export const CodeEditor = ({
|
||||
@@ -36,6 +38,7 @@ export const CodeEditor = ({
|
||||
: language === "json"
|
||||
? json()
|
||||
: StreamLanguage.define(properties),
|
||||
props.lineWrapping ? EditorView.lineWrapping : [],
|
||||
]}
|
||||
{...props}
|
||||
editable={!props.disabled}
|
||||
|
||||
Reference in New Issue
Block a user