mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -4,5 +4,8 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.biome": "explicit",
|
"source.fixAll.biome": "explicit",
|
||||||
"source.organizeImports.biome": "explicit"
|
"source.organizeImports.biome": "explicit"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ COPY --from=buildpacksio/pack:0.39.1 /usr/local/bin/pack /usr/local/bin/pack
|
|||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
HEALTHCHECK --interval=10s --timeout=3s --retries=10 \
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=5 \
|
||||||
CMD curl -fs http://localhost:3000/api/trpc/settings.health || exit 1
|
CMD curl -fs http://localhost:3000/api/trpc/settings.health || exit 1
|
||||||
|
|
||||||
CMD ["sh", "-c", "pnpm run wait-for-postgres && exec pnpm start"]
|
CMD ["sh", "-c", "pnpm run wait-for-postgres && exec pnpm start"]
|
||||||
|
|||||||
@@ -494,4 +494,49 @@ describe("processTemplate", () => {
|
|||||||
expect(result.mounts).toHaveLength(1);
|
expect(result.mounts).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("isolated deployment config", () => {
|
||||||
|
it("should default to isolated=true when not specified", () => {
|
||||||
|
const template: CompleteTemplate = {
|
||||||
|
metadata: {} as any,
|
||||||
|
variables: {},
|
||||||
|
config: {
|
||||||
|
domains: [],
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(template.config.isolated).toBeUndefined();
|
||||||
|
// undefined !== false => isolatedDeployment = true
|
||||||
|
expect(template.config.isolated !== false).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be isolated when isolated=true is explicitly set", () => {
|
||||||
|
const template: CompleteTemplate = {
|
||||||
|
metadata: {} as any,
|
||||||
|
variables: {},
|
||||||
|
config: {
|
||||||
|
isolated: true,
|
||||||
|
domains: [],
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(template.config.isolated !== false).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should disable isolated deployment when isolated=false", () => {
|
||||||
|
const template: CompleteTemplate = {
|
||||||
|
metadata: {} as any,
|
||||||
|
variables: {},
|
||||||
|
config: {
|
||||||
|
isolated: false,
|
||||||
|
domains: [],
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(template.config.isolated !== false).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ describe("helpers functions", () => {
|
|||||||
const domain = processValue("${domain}", {}, mockSchema);
|
const domain = processValue("${domain}", {}, mockSchema);
|
||||||
expect(domain.startsWith(`${mockSchema.projectName}-`)).toBeTruthy();
|
expect(domain.startsWith(`${mockSchema.projectName}-`)).toBeTruthy();
|
||||||
expect(
|
expect(
|
||||||
domain.endsWith(
|
domain.endsWith(`${mockSchema.serverIp.replaceAll(".", "-")}.sslip.io`),
|
||||||
`${mockSchema.serverIp.replaceAll(".", "-")}.traefik.me`,
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import type { RouterOutputs } from "@/utils/api";
|
import type { RouterOutputs } from "@/utils/api";
|
||||||
import type { ValidationStates } from "./show-domains";
|
|
||||||
import { AddDomain } from "./handle-domain";
|
|
||||||
import { DnsHelperModal } from "./dns-helper-modal";
|
import { DnsHelperModal } from "./dns-helper-modal";
|
||||||
|
import { AddDomain } from "./handle-domain";
|
||||||
|
import type { ValidationStates } from "./show-domains";
|
||||||
|
|
||||||
export type Domain =
|
export type Domain =
|
||||||
| RouterOutputs["domain"]["byApplicationId"][0]
|
| RouterOutputs["domain"]["byApplicationId"][0]
|
||||||
@@ -168,7 +168,7 @@ export const createColumns = ({
|
|||||||
{domain.certificateType}
|
{domain.certificateType}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{!domain.host.includes("traefik.me") && (
|
{!domain.host.includes("sslip.io") && (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@@ -256,7 +256,7 @@ export const createColumns = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{!domain.host.includes("traefik.me") && (
|
{!domain.host.includes("sslip.io") && (
|
||||||
<DnsHelperModal
|
<DnsHelperModal
|
||||||
domain={{
|
domain={{
|
||||||
host: domain.host,
|
host: domain.host,
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
const https = form.watch("https");
|
const https = form.watch("https");
|
||||||
const domainType = form.watch("domainType");
|
const domainType = form.watch("domainType");
|
||||||
const host = form.watch("host");
|
const host = form.watch("host");
|
||||||
const isTraefikMeDomain = host?.includes("traefik.me") || false;
|
const isTraefikMeDomain = host?.includes("sslip.io") || false;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -513,7 +513,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
{!canGenerateTraefikMeDomains &&
|
{!canGenerateTraefikMeDomains &&
|
||||||
field.value.includes("traefik.me") && (
|
field.value.includes("sslip.io") && (
|
||||||
<AlertBlock type="warning">
|
<AlertBlock type="warning">
|
||||||
You need to set an IP address in your{" "}
|
You need to set an IP address in your{" "}
|
||||||
<Link
|
<Link
|
||||||
@@ -524,12 +524,12 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
|
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
|
||||||
: "Web Server -> Server -> Update Server IP"}
|
: "Web Server -> Server -> Update Server IP"}
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
to make your traefik.me domain work.
|
to make your sslip.io domain work.
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
)}
|
)}
|
||||||
{isTraefikMeDomain && (
|
{isTraefikMeDomain && (
|
||||||
<AlertBlock type="info">
|
<AlertBlock type="info">
|
||||||
<strong>Note:</strong> traefik.me is a public HTTP
|
<strong>Note:</strong> sslip.io is a public HTTP
|
||||||
service and does not support SSL/HTTPS. HTTPS and
|
service and does not support SSL/HTTPS. HTTPS and
|
||||||
certificate options will not have any effect.
|
certificate options will not have any effect.
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
@@ -567,7 +567,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
sideOffset={5}
|
sideOffset={5}
|
||||||
className="max-w-[10rem]"
|
className="max-w-[10rem]"
|
||||||
>
|
>
|
||||||
<p>Generate traefik.me domain</p>
|
<p>Generate sslip.io domain</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ export const ShowDomains = ({ id, type }: Props) => {
|
|||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
<div className="flex gap-2 flex-wrap">
|
<div className="flex gap-2 flex-wrap">
|
||||||
{!item.host.includes("traefik.me") && (
|
{!item.host.includes("sslip.io") && (
|
||||||
<DnsHelperModal
|
<DnsHelperModal
|
||||||
domain={{
|
domain={{
|
||||||
host: item.host,
|
host: item.host,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||||
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -57,7 +58,10 @@ const BitbucketProviderSchema = z.object({
|
|||||||
slug: z.string().optional(),
|
slug: z.string().optional(),
|
||||||
})
|
})
|
||||||
.required(),
|
.required(),
|
||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Branch is required")
|
||||||
|
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||||
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().optional(),
|
enableSubmodules: z.boolean().optional(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||||
import { GitIcon } from "@/components/icons/data-tools-icons";
|
import { GitIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -41,7 +42,10 @@ const GitProviderSchema = z.object({
|
|||||||
repositoryURL: z.string().min(1, {
|
repositoryURL: z.string().min(1, {
|
||||||
message: "Repository URL is required",
|
message: "Repository URL is required",
|
||||||
}),
|
}),
|
||||||
branch: z.string().min(1, "Branch required"),
|
branch: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Branch required")
|
||||||
|
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||||
sshKey: z.string().optional(),
|
sshKey: z.string().optional(),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
enableSubmodules: z.boolean().default(false),
|
||||||
@@ -107,110 +111,103 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 items-start">
|
||||||
className="flex flex-col gap-4"
|
<FormField
|
||||||
>
|
control={form.control}
|
||||||
<div className="grid md:grid-cols-2 gap-4">
|
name="repositoryURL"
|
||||||
<div className="flex items-end col-span-2 gap-4">
|
render={({ field }) => (
|
||||||
<div className="grow">
|
<FormItem className="col-span-2 lg:col-span-3">
|
||||||
<FormField
|
<div className="flex items-center justify-between h-5">
|
||||||
control={form.control}
|
<FormLabel>Repository URL</FormLabel>
|
||||||
name="repositoryURL"
|
{field.value?.startsWith("https://") && (
|
||||||
render={({ field }) => (
|
<Link
|
||||||
<FormItem>
|
href={field.value}
|
||||||
<div className="flex items-center justify-between">
|
target="_blank"
|
||||||
<FormLabel>Repository URL</FormLabel>
|
rel="noopener noreferrer"
|
||||||
{field.value?.startsWith("https://") && (
|
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
|
||||||
<Link
|
>
|
||||||
href={field.value}
|
<GitIcon className="h-4 w-4" />
|
||||||
target="_blank"
|
<span>View Repository</span>
|
||||||
rel="noopener noreferrer"
|
</Link>
|
||||||
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
|
)}
|
||||||
>
|
</div>
|
||||||
<GitIcon className="h-4 w-4" />
|
<FormControl>
|
||||||
<span>View Repository</span>
|
<Input placeholder="Repository URL" {...field} />
|
||||||
</Link>
|
</FormControl>
|
||||||
)}
|
<FormMessage />
|
||||||
</div>
|
</FormItem>
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="Repository URL" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{sshKeys && sshKeys.length > 0 ? (
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="sshKey"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="basis-40">
|
|
||||||
<FormLabel className="w-full inline-flex justify-between">
|
|
||||||
SSH Key
|
|
||||||
<LockIcon className="size-4 text-muted-foreground" />
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select
|
|
||||||
key={field.value}
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
defaultValue={field.value}
|
|
||||||
value={field.value}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select a key" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
{sshKeys?.map((sshKey) => (
|
|
||||||
<SelectItem
|
|
||||||
key={sshKey.sshKeyId}
|
|
||||||
value={sshKey.sshKeyId}
|
|
||||||
>
|
|
||||||
{sshKey.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
<SelectItem value="none">None</SelectItem>
|
|
||||||
<SelectLabel>Keys ({sshKeys?.length})</SelectLabel>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
onClick={() => router.push("/dashboard/settings/ssh-keys")}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<KeyRoundIcon className="size-4" /> Add SSH Key
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
/>
|
||||||
<div className="space-y-4">
|
{sshKeys && sshKeys.length > 0 ? (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="branch"
|
name="sshKey"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem className="col-span-2 lg:col-span-1">
|
||||||
<FormLabel>Branch</FormLabel>
|
<FormLabel className="w-full inline-flex justify-between">
|
||||||
|
SSH Key
|
||||||
|
<LockIcon className="size-4 text-muted-foreground" />
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Branch" {...field} />
|
<Select
|
||||||
|
key={field.value}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value}
|
||||||
|
value={field.value}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a key" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{sshKeys?.map((sshKey) => (
|
||||||
|
<SelectItem
|
||||||
|
key={sshKey.sshKeyId}
|
||||||
|
value={sshKey.sshKeyId}
|
||||||
|
>
|
||||||
|
{sshKey.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectItem value="none">None</SelectItem>
|
||||||
|
<SelectLabel>Keys ({sshKeys?.length})</SelectLabel>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => router.push("/dashboard/settings/ssh-keys")}
|
||||||
|
type="button"
|
||||||
|
className="col-span-2 lg:col-span-1 lg:mt-7"
|
||||||
|
>
|
||||||
|
<KeyRoundIcon className="size-4" /> Add SSH Key
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="branch"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="col-span-2">
|
||||||
|
<FormLabel>Branch</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Branch" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="buildPath"
|
name="buildPath"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem className="col-span-2">
|
||||||
<FormLabel>Build Path</FormLabel>
|
<FormLabel>Build Path</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="/" {...field} />
|
<Input placeholder="/" {...field} />
|
||||||
@@ -223,7 +220,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="watchPaths"
|
name="watchPaths"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="md:col-span-2">
|
<FormItem className="col-span-2 lg:col-span-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FormLabel>Watch Paths</FormLabel>
|
<FormLabel>Watch Paths</FormLabel>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||||
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -72,7 +73,10 @@ const GiteaProviderSchema = z.object({
|
|||||||
owner: z.string().min(1, "Owner is required"),
|
owner: z.string().min(1, "Owner is required"),
|
||||||
})
|
})
|
||||||
.required(),
|
.required(),
|
||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Branch is required")
|
||||||
|
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||||
giteaId: z.string().min(1, "Gitea Provider is required"),
|
giteaId: z.string().min(1, "Gitea Provider is required"),
|
||||||
watchPaths: z.array(z.string()).default([]),
|
watchPaths: z.array(z.string()).default([]),
|
||||||
enableSubmodules: z.boolean().optional(),
|
enableSubmodules: z.boolean().optional(),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||||
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -55,7 +56,10 @@ const GithubProviderSchema = z.object({
|
|||||||
owner: z.string().min(1, "Owner is required"),
|
owner: z.string().min(1, "Owner is required"),
|
||||||
})
|
})
|
||||||
.required(),
|
.required(),
|
||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Branch is required")
|
||||||
|
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||||
githubId: z.string().min(1, "Github Provider is required"),
|
githubId: z.string().min(1, "Github Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
triggerType: z.enum(["push", "tag"]).default("push"),
|
triggerType: z.enum(["push", "tag"]).default("push"),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect, useMemo } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -58,7 +59,10 @@ const GitlabProviderSchema = z.object({
|
|||||||
id: z.number().nullable(),
|
id: z.number().nullable(),
|
||||||
})
|
})
|
||||||
.required(),
|
.required(),
|
||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Branch is required")
|
||||||
|
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||||
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
enableSubmodules: z.boolean().default(false),
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl">Deploy Settings</CardTitle>
|
<CardTitle className="text-xl">Deploy Settings</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-row gap-4 flex-wrap">
|
<CardContent className="grid grid-cols-2 lg:flex lg:flex-row lg:flex-wrap gap-4">
|
||||||
<TooltipProvider delayDuration={0} disableHoverableContent={false}>
|
<TooltipProvider delayDuration={0} disableHoverableContent={false}>
|
||||||
{canDeploy && (
|
{canDeploy && (
|
||||||
<DialogAction
|
<DialogAction
|
||||||
@@ -274,14 +274,14 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
|
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2 col-span-2"
|
||||||
>
|
>
|
||||||
<Terminal className="size-4 mr-1" />
|
<Terminal className="size-4 mr-1" />
|
||||||
Open Terminal
|
Open Terminal
|
||||||
</Button>
|
</Button>
|
||||||
</DockerTerminalModal>
|
</DockerTerminalModal>
|
||||||
{canUpdateService && (
|
{canUpdateService && (
|
||||||
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
|
<div className="flex flex-row items-center gap-2 justify-between rounded-md px-4 py-2 border col-span-2 md:col-span-1">
|
||||||
<span className="text-sm font-medium">Autodeploy</span>
|
<span className="text-sm font-medium">Autodeploy</span>
|
||||||
<Switch
|
<Switch
|
||||||
aria-label="Toggle autodeploy"
|
aria-label="Toggle autodeploy"
|
||||||
@@ -305,7 +305,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{canUpdateService && (
|
{canUpdateService && (
|
||||||
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
|
<div className="flex flex-row items-center gap-2 justify-between rounded-md px-4 py-2 border col-span-2 md:col-span-1">
|
||||||
<span className="text-sm font-medium">Clean Cache</span>
|
<span className="text-sm font-medium">Clean Cache</span>
|
||||||
<Switch
|
<Switch
|
||||||
aria-label="Toggle clean cache"
|
aria-label="Toggle clean cache"
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const AddPreviewDomain = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const host = form.watch("host");
|
const host = form.watch("host");
|
||||||
const isTraefikMeDomain = host?.includes("traefik.me") || false;
|
const isTraefikMeDomain = host?.includes("sslip.io") || false;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -162,7 +162,7 @@ export const AddPreviewDomain = ({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
{isTraefikMeDomain && (
|
{isTraefikMeDomain && (
|
||||||
<AlertBlock type="info">
|
<AlertBlock type="info">
|
||||||
<strong>Note:</strong> traefik.me is a public HTTP
|
<strong>Note:</strong> sslip.io is a public HTTP
|
||||||
service and does not support SSL/HTTPS. HTTPS and
|
service and does not support SSL/HTTPS. HTTPS and
|
||||||
certificate options will not have any effect.
|
certificate options will not have any effect.
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
@@ -202,7 +202,7 @@ export const AddPreviewDomain = ({
|
|||||||
sideOffset={5}
|
sideOffset={5}
|
||||||
className="max-w-[10rem]"
|
className="max-w-[10rem]"
|
||||||
>
|
>
|
||||||
<p>Generate traefik.me domain</p>
|
<p>Generate sslip.io domain</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
const form = useForm<Schema>({
|
const form = useForm<Schema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
env: "",
|
env: "",
|
||||||
wildcardDomain: "*.traefik.me",
|
wildcardDomain: "*.sslip.io",
|
||||||
port: 3000,
|
port: 3000,
|
||||||
previewLimit: 3,
|
previewLimit: 3,
|
||||||
previewLabels: [],
|
previewLabels: [],
|
||||||
@@ -102,7 +102,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
|
|
||||||
const previewHttps = form.watch("previewHttps");
|
const previewHttps = form.watch("previewHttps");
|
||||||
const wildcardDomain = form.watch("wildcardDomain");
|
const wildcardDomain = form.watch("wildcardDomain");
|
||||||
const isTraefikMeDomain = wildcardDomain?.includes("traefik.me") || false;
|
const isTraefikMeDomain = wildcardDomain?.includes("sslip.io") || false;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsEnabled(data?.isPreviewDeploymentsActive || false);
|
setIsEnabled(data?.isPreviewDeploymentsActive || false);
|
||||||
@@ -114,7 +114,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
env: data.previewEnv || "",
|
env: data.previewEnv || "",
|
||||||
buildArgs: data.previewBuildArgs || "",
|
buildArgs: data.previewBuildArgs || "",
|
||||||
buildSecrets: data.previewBuildSecrets || "",
|
buildSecrets: data.previewBuildSecrets || "",
|
||||||
wildcardDomain: data.previewWildcard || "*.traefik.me",
|
wildcardDomain: data.previewWildcard || "*.sslip.io",
|
||||||
port: data.previewPort || 3000,
|
port: data.previewPort || 3000,
|
||||||
previewLabels: data.previewLabels || [],
|
previewLabels: data.previewLabels || [],
|
||||||
previewLimit: data.previewLimit || 3,
|
previewLimit: data.previewLimit || 3,
|
||||||
@@ -173,7 +173,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
{isTraefikMeDomain && (
|
{isTraefikMeDomain && (
|
||||||
<AlertBlock type="info">
|
<AlertBlock type="info">
|
||||||
<strong>Note:</strong> traefik.me is a public HTTP service and
|
<strong>Note:</strong> sslip.io is a public HTTP service and
|
||||||
does not support SSL/HTTPS. HTTPS and certificate options will
|
does not support SSL/HTTPS. HTTPS and certificate options will
|
||||||
not have any effect.
|
not have any effect.
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
@@ -192,7 +192,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Wildcard Domain</FormLabel>
|
<FormLabel>Wildcard Domain</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="*.traefik.me" {...field} />
|
<Input placeholder="*.sslip.io" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export const commonCronExpressions = [
|
|||||||
const formSchema = z
|
const formSchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().min(1, "Name is required"),
|
name: z.string().min(1, "Name is required"),
|
||||||
|
description: z.string().optional(),
|
||||||
cronExpression: z.string().min(1, "Cron expression is required"),
|
cronExpression: z.string().min(1, "Cron expression is required"),
|
||||||
shellType: z.enum(["bash", "sh"]).default("bash"),
|
shellType: z.enum(["bash", "sh"]).default("bash"),
|
||||||
command: z.string(),
|
command: z.string(),
|
||||||
@@ -224,6 +225,7 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
|||||||
resolver: standardSchemaResolver(formSchema),
|
resolver: standardSchemaResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
|
description: "",
|
||||||
cronExpression: "",
|
cronExpression: "",
|
||||||
shellType: "bash",
|
shellType: "bash",
|
||||||
command: "",
|
command: "",
|
||||||
@@ -263,6 +265,7 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
|||||||
if (scheduleId && schedule) {
|
if (scheduleId && schedule) {
|
||||||
form.reset({
|
form.reset({
|
||||||
name: schedule.name,
|
name: schedule.name,
|
||||||
|
description: schedule.description || "",
|
||||||
cronExpression: schedule.cronExpression,
|
cronExpression: schedule.cronExpression,
|
||||||
shellType: schedule.shellType,
|
shellType: schedule.shellType,
|
||||||
command: schedule.command,
|
command: schedule.command,
|
||||||
@@ -479,6 +482,26 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Description</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Backs up the database every day at midnight"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Optional description of what this schedule does
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<ScheduleFormField
|
<ScheduleFormField
|
||||||
name="cronExpression"
|
name="cronExpression"
|
||||||
formControl={form.control}
|
formControl={form.control}
|
||||||
|
|||||||
@@ -125,6 +125,11 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
{schedule.enabled ? "Enabled" : "Disabled"}
|
{schedule.enabled ? "Enabled" : "Disabled"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
{schedule.description && (
|
||||||
|
<p className="text-xs text-muted-foreground/70 [overflow-wrap:anywhere] line-clamp-2">
|
||||||
|
{schedule.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground flex-wrap">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground flex-wrap">
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import { Loader2, MoreHorizontal, RefreshCw } from "lucide-react";
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { ShowContainerConfig } from "@/components/dashboard/docker/config/show-container-config";
|
||||||
|
import { ShowContainerMounts } from "@/components/dashboard/docker/mounts/show-container-mounts";
|
||||||
|
import { ShowContainerNetworks } from "@/components/dashboard/docker/networks/show-container-networks";
|
||||||
|
import { DockerTerminalModal } from "@/components/dashboard/docker/terminal/docker-terminal-modal";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -36,10 +40,6 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { ShowContainerConfig } from "@/components/dashboard/docker/config/show-container-config";
|
|
||||||
import { ShowContainerMounts } from "@/components/dashboard/docker/mounts/show-container-mounts";
|
|
||||||
import { ShowContainerNetworks } from "@/components/dashboard/docker/networks/show-container-networks";
|
|
||||||
import { DockerTerminalModal } from "@/components/dashboard/docker/terminal/docker-terminal-modal";
|
|
||||||
|
|
||||||
const DockerLogsId = dynamic(
|
const DockerLogsId = dynamic(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -49,12 +49,12 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
|
|||||||
const composeFile = form.watch("composeFile");
|
const composeFile = form.watch("composeFile");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data && !composeFile) {
|
if (data) {
|
||||||
form.reset({
|
form.reset({
|
||||||
composeFile: data.composeFile || "",
|
composeFile: data.composeFile || "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form, form.reset, data]);
|
}, [form, data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.composeFile !== undefined) {
|
if (data?.composeFile !== undefined) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||||
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -57,7 +58,10 @@ const BitbucketProviderSchema = z.object({
|
|||||||
slug: z.string().optional(),
|
slug: z.string().optional(),
|
||||||
})
|
})
|
||||||
.required(),
|
.required(),
|
||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Branch is required")
|
||||||
|
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||||
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
bitbucketId: z.string().min(1, "Bitbucket Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
enableSubmodules: z.boolean().default(false),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||||
import { GitIcon } from "@/components/icons/data-tools-icons";
|
import { GitIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -41,7 +42,10 @@ const GitProviderSchema = z.object({
|
|||||||
repositoryURL: z.string().min(1, {
|
repositoryURL: z.string().min(1, {
|
||||||
message: "Repository URL is required",
|
message: "Repository URL is required",
|
||||||
}),
|
}),
|
||||||
branch: z.string().min(1, "Branch required"),
|
branch: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Branch required")
|
||||||
|
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||||
sshKey: z.string().optional(),
|
sshKey: z.string().optional(),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
enableSubmodules: z.boolean().default(false),
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { CheckIcon, ChevronsUpDown, Plus, X, HelpCircle } from "lucide-react";
|
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||||
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -57,7 +58,10 @@ const GiteaProviderSchema = z.object({
|
|||||||
owner: z.string().min(1, "Owner is required"),
|
owner: z.string().min(1, "Owner is required"),
|
||||||
})
|
})
|
||||||
.required(),
|
.required(),
|
||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Branch is required")
|
||||||
|
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||||
giteaId: z.string().min(1, "Gitea Provider is required"),
|
giteaId: z.string().min(1, "Gitea Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
enableSubmodules: z.boolean().default(false),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { CheckIcon, ChevronsUpDown, HelpCircle, X } from "lucide-react";
|
import { CheckIcon, ChevronsUpDown, HelpCircle, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -55,7 +56,10 @@ const GithubProviderSchema = z.object({
|
|||||||
owner: z.string().min(1, "Owner is required"),
|
owner: z.string().min(1, "Owner is required"),
|
||||||
})
|
})
|
||||||
.required(),
|
.required(),
|
||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Branch is required")
|
||||||
|
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||||
githubId: z.string().min(1, "Github Provider is required"),
|
githubId: z.string().min(1, "Github Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
triggerType: z.enum(["push", "tag"]).default("push"),
|
triggerType: z.enum(["push", "tag"]).default("push"),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect, useMemo } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -58,7 +59,10 @@ const GitlabProviderSchema = z.object({
|
|||||||
gitlabPathNamespace: z.string().min(1),
|
gitlabPathNamespace: z.string().min(1),
|
||||||
})
|
})
|
||||||
.required(),
|
.required(),
|
||||||
branch: z.string().min(1, "Branch is required"),
|
branch: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Branch is required")
|
||||||
|
.regex(VALID_BRANCH_REGEX, "Invalid branch name"),
|
||||||
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
gitlabId: z.string().min(1, "Gitlab Provider is required"),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
enableSubmodules: z.boolean().default(false),
|
enableSubmodules: z.boolean().default(false),
|
||||||
|
|||||||
@@ -288,7 +288,6 @@ export const RestoreBackup = ({
|
|||||||
toast.error("Please select a database type");
|
toast.error("Please select a database type");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log({ data });
|
|
||||||
setIsDeploying(true);
|
setIsDeploying(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Bot, Loader2, RotateCcw, Settings, X } from "lucide-react";
|
import copy from "copy-to-clipboard";
|
||||||
|
import {
|
||||||
|
Bot,
|
||||||
|
Check,
|
||||||
|
Copy,
|
||||||
|
Loader2,
|
||||||
|
RotateCcw,
|
||||||
|
Settings,
|
||||||
|
X,
|
||||||
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
@@ -30,6 +39,7 @@ const MAX_LOG_LINES = 200;
|
|||||||
export function AnalyzeLogs({ logs, context }: Props) {
|
export function AnalyzeLogs({ logs, context }: Props) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [aiId, setAiId] = useState<string>("");
|
const [aiId, setAiId] = useState<string>("");
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
const { data: providers } = api.ai.getEnabledProviders.useQuery(undefined, {
|
const { data: providers } = api.ai.getEnabledProviders.useQuery(undefined, {
|
||||||
enabled: open,
|
enabled: open,
|
||||||
});
|
});
|
||||||
@@ -52,6 +62,15 @@ export function AnalyzeLogs({ logs, context }: Props) {
|
|||||||
mutate({ aiId, logs: logsText, context });
|
mutate({ aiId, logs: logsText, context });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCopy = () => {
|
||||||
|
if (!data?.analysis) return;
|
||||||
|
const success = copy(data.analysis);
|
||||||
|
if (success) {
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
open={open}
|
open={open}
|
||||||
@@ -168,6 +187,18 @@ export function AnalyzeLogs({ logs, context }: Props) {
|
|||||||
)}
|
)}
|
||||||
Re-analyze
|
Re-analyze
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleCopy}
|
||||||
|
title="Copy analysis to clipboard"
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<Check className="h-3.5 w-3.5" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-3.5 w-3.5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -15,7 +16,6 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -15,7 +16,6 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import {
|
import {
|
||||||
uploadFileToContainerSchema,
|
|
||||||
type UploadFileToContainer,
|
type UploadFileToContainer,
|
||||||
|
uploadFileToContainerSchema,
|
||||||
} from "@/utils/schema";
|
} from "@/utils/schema";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { toast } from "sonner";
|
||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mariadbId: string;
|
mariadbId: string;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { toast } from "sonner";
|
||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mongoId: string;
|
mongoId: string;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { toast } from "sonner";
|
||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mysqlId: string;
|
mysqlId: string;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { toast } from "sonner";
|
||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
postgresId: string;
|
postgresId: string;
|
||||||
|
|||||||
@@ -632,7 +632,6 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="enableNamespaces"
|
name="enableNamespaces"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
console.log(field.value);
|
|
||||||
return (
|
return (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Enable Namespaces</FormLabel>
|
<FormLabel>Enable Namespaces</FormLabel>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
BookText,
|
|
||||||
Bookmark,
|
Bookmark,
|
||||||
|
BookText,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
ChevronsUpDown,
|
ChevronsUpDown,
|
||||||
Globe,
|
Globe,
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ export const ShowProjects = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border">
|
<Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border flex flex-col">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center justify-between gap-2 overflow-clip">
|
<CardTitle className="flex items-center justify-between gap-2 overflow-clip">
|
||||||
<span className="flex flex-col gap-1.5 ">
|
<span className="flex flex-col gap-1.5 ">
|
||||||
@@ -491,7 +491,7 @@ export const ShowProjects = () => {
|
|||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardFooter className="pt-4">
|
<CardFooter className="pt-4 mt-auto">
|
||||||
<div className="space-y-1 text-xs flex flex-row justify-between max-sm:flex-wrap w-full gap-2 sm:gap-4">
|
<div className="space-y-1 text-xs flex flex-row justify-between max-sm:flex-wrap w-full gap-2 sm:gap-4">
|
||||||
<DateTooltip date={project.createdAt}>
|
<DateTooltip date={project.createdAt}>
|
||||||
Created
|
Created
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { toast } from "sonner";
|
||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
import { UpdateDatabasePassword } from "@/components/shared/update-database-password";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
redisId: string;
|
redisId: string;
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { NumberInput } from "@/components/ui/input";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -34,6 +33,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import { NumberInput } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { HelpCircle } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import {
|
import {
|
||||||
@@ -7,8 +9,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { HelpCircle } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { EnterpriseFeatureLocked } from "@/components/proprietary/enterprise-feature-gate";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
@@ -26,7 +27,6 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { EnterpriseFeatureLocked } from "@/components/proprietary/enterprise-feature-gate";
|
|
||||||
import { api, type RouterOutputs } from "@/utils/api";
|
import { api, type RouterOutputs } from "@/utils/api";
|
||||||
|
|
||||||
/** Shape returned by project.allForPermissions (admin only). Used for the permissions UI. */
|
/** Shape returned by project.allForPermissions (admin only). Used for the permissions UI. */
|
||||||
|
|||||||
@@ -141,14 +141,14 @@ export const WebDomain = () => {
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="grid w-full gap-4 md:grid-cols-2"
|
className="grid w-full gap-4 grid-cols-2"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="domain"
|
name="domain"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<FormItem>
|
<FormItem className="col-span-2 md:col-span-1">
|
||||||
<FormLabel>Domain</FormLabel>
|
<FormLabel>Domain</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
@@ -168,7 +168,7 @@ export const WebDomain = () => {
|
|||||||
name="letsEncryptEmail"
|
name="letsEncryptEmail"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<FormItem>
|
<FormItem className="col-span-2 md:col-span-1">
|
||||||
<FormLabel>Let's Encrypt Email</FormLabel>
|
<FormLabel>Let's Encrypt Email</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
@@ -209,7 +209,7 @@ export const WebDomain = () => {
|
|||||||
name="certificateType"
|
name="certificateType"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<FormItem className="md:col-span-2">
|
<FormItem className="col-span-2">
|
||||||
<FormLabel>Certificate Provider</FormLabel>
|
<FormLabel>Certificate Provider</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
|
|||||||
@@ -868,6 +868,19 @@ function SidebarLogo() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MobileCloser() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const { setOpenMobile, isMobile } = useSidebar();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMobile) {
|
||||||
|
setOpenMobile(false);
|
||||||
|
}
|
||||||
|
}, [pathname, isMobile, setOpenMobile]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export default function Page({ children }: Props) {
|
export default function Page({ children }: Props) {
|
||||||
const [defaultOpen, setDefaultOpen] = useState<boolean | undefined>(
|
const [defaultOpen, setDefaultOpen] = useState<boolean | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
@@ -933,6 +946,7 @@ export default function Page({ children }: Props) {
|
|||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<MobileCloser />
|
||||||
<Sidebar collapsible="icon" variant="floating">
|
<Sidebar collapsible="icon" variant="floating">
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
{/* <SidebarMenuButton
|
{/* <SidebarMenuButton
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ export const AdvanceBreadcrumb = () => {
|
|||||||
className="h-auto px-2 py-1.5 hover:bg-accent gap-2"
|
className="h-auto px-2 py-1.5 hover:bg-accent gap-2"
|
||||||
>
|
>
|
||||||
<FolderInput className="size-4 text-muted-foreground" />
|
<FolderInput className="size-4 text-muted-foreground" />
|
||||||
<span className="font-medium max-w-[150px] truncate">
|
<span className="font-medium max-w-[50px] md:max-w-[150px] truncate">
|
||||||
{currentProject?.name || "Select Project"}
|
{currentProject?.name || "Select Project"}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown className="size-4 text-muted-foreground" />
|
<ChevronDown className="size-4 text-muted-foreground" />
|
||||||
@@ -478,7 +478,7 @@ export const AdvanceBreadcrumb = () => {
|
|||||||
aria-expanded={environmentOpen}
|
aria-expanded={environmentOpen}
|
||||||
className="h-auto px-2 py-1.5 hover:bg-accent gap-2"
|
className="h-auto px-2 py-1.5 hover:bg-accent gap-2"
|
||||||
>
|
>
|
||||||
<span className="font-medium max-w-[150px] truncate">
|
<span className="font-medium max-w-[50px] md:max-w-[150px] truncate">
|
||||||
{currentEnvironment?.name || "production"}
|
{currentEnvironment?.name || "production"}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown className="size-4 text-muted-foreground" />
|
<ChevronDown className="size-4 text-muted-foreground" />
|
||||||
@@ -533,7 +533,7 @@ export const AdvanceBreadcrumb = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{projectEnvironments && projectEnvironments.length === 1 && (
|
{projectEnvironments && projectEnvironments.length === 1 && (
|
||||||
<p className="text-sm font-normal ml-1">
|
<p className="text-sm font-normal ml-1 max-w-[50px] md:max-w-[150px] truncate">
|
||||||
{currentEnvironment?.name || "production"}
|
{currentEnvironment?.name || "production"}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -551,7 +551,7 @@ export const AdvanceBreadcrumb = () => {
|
|||||||
className="h-auto px-2 py-1.5 hover:bg-accent gap-2"
|
className="h-auto px-2 py-1.5 hover:bg-accent gap-2"
|
||||||
>
|
>
|
||||||
{getServiceIcon(currentService.type)}
|
{getServiceIcon(currentService.type)}
|
||||||
<span className="font-medium max-w-[150px] truncate">
|
<span className="font-medium max-w-[50px] md:max-w-[150px] truncate">
|
||||||
{currentService.name}
|
{currentService.name}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown className="size-4 text-muted-foreground" />
|
<ChevronDown className="size-4 text-muted-foreground" />
|
||||||
@@ -617,7 +617,7 @@ export const AdvanceBreadcrumb = () => {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="size-7 ml-1"
|
className="size-7 ml-1 hidden md:flex"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push(
|
router.push(
|
||||||
`/dashboard/project/${projectId}/environment/${environmentId}`,
|
`/dashboard/project/${projectId}/environment/${environmentId}`,
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
className={cn(
|
className={cn(
|
||||||
buttonVariants({ variant, size, className }),
|
buttonVariants({ variant, size, className }),
|
||||||
"flex gap-2",
|
"flex gap-2",
|
||||||
|
className,
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
1
apps/dokploy/drizzle/0166_nosy_slapstick.sql
Normal file
1
apps/dokploy/drizzle/0166_nosy_slapstick.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "schedule" ADD COLUMN "description" text;
|
||||||
8318
apps/dokploy/drizzle/meta/0166_snapshot.json
Normal file
8318
apps/dokploy/drizzle/meta/0166_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1163,6 +1163,13 @@
|
|||||||
"when": 1775845419261,
|
"when": 1775845419261,
|
||||||
"tag": "0165_abnormal_greymalkin",
|
"tag": "0165_abnormal_greymalkin",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 166,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1778303519111,
|
||||||
|
"tag": "0166_nosy_slapstick",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,7 @@ try {
|
|||||||
"wait-for-postgres": "wait-for-postgres.ts",
|
"wait-for-postgres": "wait-for-postgres.ts",
|
||||||
"reset-password": "reset-password.ts",
|
"reset-password": "reset-password.ts",
|
||||||
"reset-2fa": "reset-2fa.ts",
|
"reset-2fa": "reset-2fa.ts",
|
||||||
|
"migrate-auth-secret": "scripts/migrate-auth-secret.ts",
|
||||||
},
|
},
|
||||||
bundle: true,
|
bundle: true,
|
||||||
platform: "node",
|
platform: "node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.29.2",
|
"version": "v0.29.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"wait-for-postgres-dev": "tsx -r dotenv/config wait-for-postgres.ts",
|
"wait-for-postgres-dev": "tsx -r dotenv/config wait-for-postgres.ts",
|
||||||
"reset-password": "node -r dotenv/config dist/reset-password.mjs",
|
"reset-password": "node -r dotenv/config dist/reset-password.mjs",
|
||||||
"reset-2fa": "node -r dotenv/config dist/reset-2fa.mjs",
|
"reset-2fa": "node -r dotenv/config dist/reset-2fa.mjs",
|
||||||
|
"migrate-auth-secret": "node -r dotenv/config dist/migrate-auth-secret.mjs",
|
||||||
"dev": "tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
|
"dev": "tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
|
||||||
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
||||||
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
|
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
|
||||||
@@ -126,7 +127,7 @@
|
|||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"nextjs-toploader": "^3.9.17",
|
"nextjs-toploader": "^3.9.17",
|
||||||
"node-os-utils": "2.0.1",
|
"node-os-utils": "2.0.1",
|
||||||
"node-pty": "1.0.0",
|
"node-pty": "1.1.0",
|
||||||
"node-schedule": "2.1.1",
|
"node-schedule": "2.1.1",
|
||||||
"nodemailer": "6.9.14",
|
"nodemailer": "6.9.14",
|
||||||
"octokit": "3.1.2",
|
"octokit": "3.1.2",
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ export default async function handler(
|
|||||||
res: NextApiResponse,
|
res: NextApiResponse,
|
||||||
) {
|
) {
|
||||||
const signature = req.headers["x-hub-signature-256"];
|
const signature = req.headers["x-hub-signature-256"];
|
||||||
|
if (!signature) {
|
||||||
|
res.status(401).json({ message: "Missing signature header" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const githubBody = req.body;
|
const githubBody = req.body;
|
||||||
|
|
||||||
if (!githubBody?.installation?.id) {
|
if (!githubBody?.installation?.id) {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export async function getServerSideProps(
|
|||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -104,7 +104,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1099,7 +1099,7 @@ const EnvironmentPage = (
|
|||||||
</div>
|
</div>
|
||||||
<CardContent className="space-y-2 py-8 border-t gap-4 flex flex-col min-h-[60vh]">
|
<CardContent className="space-y-2 py-8 border-t gap-4 flex flex-col min-h-[60vh]">
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
<div className="flex flex-col gap-4 2xl:flex-row 2xl:items-center 2xl:justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -1620,9 +1620,9 @@ const EnvironmentPage = (
|
|||||||
<ContextMenuTrigger asChild>
|
<ContextMenuTrigger asChild>
|
||||||
<Link
|
<Link
|
||||||
href={`/dashboard/project/${projectId}/environment/${environmentId}/services/${service.type}/${service.id}`}
|
href={`/dashboard/project/${projectId}/environment/${environmentId}/services/${service.type}/${service.id}`}
|
||||||
className="block"
|
className="block h-full"
|
||||||
>
|
>
|
||||||
<Card className="flex flex-col group relative cursor-pointer bg-transparent transition-colors hover:bg-border">
|
<Card className="flex flex-col h-full group relative cursor-pointer bg-transparent transition-colors hover:bg-border">
|
||||||
{service.serverId && (
|
{service.serverId && (
|
||||||
<div className="absolute -left-1 -top-2">
|
<div className="absolute -left-1 -top-2">
|
||||||
<ServerIcon className="size-4 text-muted-foreground" />
|
<ServerIcon className="size-4 text-muted-foreground" />
|
||||||
@@ -1827,7 +1827,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -451,7 +451,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export async function getServerSideProps(
|
|||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -26,7 +26,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export async function getServerSideProps(
|
|||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -43,7 +43,7 @@ export async function getServerSideProps(
|
|||||||
if (!user || (user.role !== "owner" && user.role !== "admin")) {
|
if (!user || (user.role !== "owner" && user.role !== "admin")) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export async function getServerSideProps(
|
|||||||
if (!user || user.role === "member") {
|
if (!user || user.role === "member") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: { destination: "/", permanent: true },
|
redirect: { destination: "/", permanent: false },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export async function getServerSideProps(
|
|||||||
if (!IS_CLOUD) {
|
if (!IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -33,7 +33,7 @@ export async function getServerSideProps(
|
|||||||
if (!user || user.role !== "owner") {
|
if (!user || user.role !== "owner") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export async function getServerSideProps(
|
|||||||
if (!user || user.role === "member") {
|
if (!user || user.role === "member") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export async function getServerSideProps(
|
|||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -36,7 +36,7 @@ export async function getServerSideProps(
|
|||||||
if (!user || user.role === "member") {
|
if (!user || user.role === "member") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export async function getServerSideProps(
|
|||||||
if (!user || user.role === "member") {
|
if (!user || user.role === "member") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -53,7 +53,7 @@ export async function getServerSideProps(
|
|||||||
if (!userPermissions?.gitProviders.read) {
|
if (!userPermissions?.gitProviders.read) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export async function getServerSideProps(
|
|||||||
if (!IS_CLOUD) {
|
if (!IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -33,7 +33,7 @@ export async function getServerSideProps(
|
|||||||
if (!user || user.role !== "owner") {
|
if (!user || user.role !== "owner") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -46,7 +46,7 @@ export async function getServerSideProps(
|
|||||||
if (user.role !== "owner") {
|
if (user.role !== "owner") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/settings/profile",
|
destination: "/dashboard/settings/profile",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export async function getServerSideProps(
|
|||||||
if (!user || user.role === "member") {
|
if (!user || user.role === "member") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export async function getServerSideProps(
|
|||||||
if (!user || user.role === "member") {
|
if (!user || user.role === "member") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export async function getServerSideProps(
|
|||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -53,7 +53,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -61,7 +61,7 @@ export async function getServerSideProps(
|
|||||||
if (user.role === "member") {
|
if (user.role === "member") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/settings/profile",
|
destination: "/dashboard/settings/profile",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -36,7 +36,7 @@ export async function getServerSideProps(
|
|||||||
if (user.role === "member") {
|
if (user.role === "member") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/settings/profile",
|
destination: "/dashboard/settings/profile",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -54,7 +54,7 @@ export async function getServerSideProps(
|
|||||||
if (!userPermissions?.sshKeys.read) {
|
if (!userPermissions?.sshKeys.read) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -54,7 +54,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
if (user.role === "member") {
|
if (user.role === "member") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/settings/profile",
|
destination: "/dashboard/settings/profile",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export async function getServerSideProps(
|
|||||||
if (!userPermissions?.tag.read) {
|
if (!userPermissions?.tag.read) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -66,7 +66,7 @@ export async function getServerSideProps(
|
|||||||
if (!userPermissions?.member.read) {
|
if (!userPermissions?.member.read) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -54,7 +54,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
if (user.role !== "owner") {
|
if (user.role !== "owner") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/settings/profile",
|
destination: "/dashboard/settings/profile",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { createServerSideHelpers } from "@trpc/react-query/server";
|
|||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import type { ReactElement } from "react";
|
import type { ReactElement } from "react";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import SwarmMonitorCard from "@/components/dashboard/swarm/monitoring-card";
|
|
||||||
import { ShowSwarmContainers } from "@/components/dashboard/swarm/containers/show-swarm-containers";
|
import { ShowSwarmContainers } from "@/components/dashboard/swarm/containers/show-swarm-containers";
|
||||||
|
import SwarmMonitorCard from "@/components/dashboard/swarm/monitoring-card";
|
||||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
@@ -45,7 +45,7 @@ export async function getServerSideProps(
|
|||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -54,7 +54,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -80,7 +80,7 @@ export async function getServerSideProps(
|
|||||||
if (!userPermissions?.docker.read) {
|
if (!userPermissions?.docker.read) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export async function getServerSideProps(
|
|||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -32,7 +32,7 @@ export async function getServerSideProps(
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -58,7 +58,7 @@ export async function getServerSideProps(
|
|||||||
if (!userPermissions?.traefikFiles.read) {
|
if (!userPermissions?.traefikFiles.read) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
if (user) {
|
if (user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -425,7 +425,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
if (!hasAdmin) {
|
if (!hasAdmin) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/register",
|
destination: "/register",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -436,7 +436,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
if (user) {
|
if (user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
// if (IS_CLOUD) {
|
// if (IS_CLOUD) {
|
||||||
// return {
|
// return {
|
||||||
// redirect: {
|
// redirect: {
|
||||||
// permanent: true,
|
// permanent: false,
|
||||||
// destination: "/",
|
// destination: "/",
|
||||||
// },
|
// },
|
||||||
// };
|
// };
|
||||||
@@ -342,7 +342,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
if (typeof token !== "string") {
|
if (typeof token !== "string") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -365,7 +365,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
if (invitation.isExpired) {
|
if (invitation.isExpired) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -382,7 +382,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
console.log("error", error);
|
console.log("error", error);
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
if (user) {
|
if (user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/dashboard/home",
|
destination: "/dashboard/home",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
if (!IS_CLOUD) {
|
if (!IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -200,7 +200,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
if (typeof token !== "string") {
|
if (typeof token !== "string") {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ const loginSchema = z.object({
|
|||||||
.min(1, {
|
.min(1, {
|
||||||
message: "Email is required",
|
message: "Email is required",
|
||||||
})
|
})
|
||||||
|
.max(255, {
|
||||||
|
message: "Email must be at most 255 characters",
|
||||||
|
})
|
||||||
.email({
|
.email({
|
||||||
message: "Email must be a valid email",
|
message: "Email must be a valid email",
|
||||||
}),
|
}),
|
||||||
@@ -118,7 +121,11 @@ export default function Home() {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Email</FormLabel>
|
<FormLabel>Email</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Email" {...field} />
|
<Input
|
||||||
|
placeholder="Email"
|
||||||
|
maxLength={255}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -161,7 +168,7 @@ export async function getServerSideProps(_context: GetServerSidePropsContext) {
|
|||||||
if (!IS_CLOUD) {
|
if (!IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -103,7 +103,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
if (!userPermissions?.api.read) {
|
if (!userPermissions?.api.read) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
permanent: true,
|
permanent: false,
|
||||||
destination: "/",
|
destination: "/",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
97
apps/dokploy/scripts/migrate-auth-secret.ts
Normal file
97
apps/dokploy/scripts/migrate-auth-secret.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* Use this command to automatically migrate the auth secret: curl -sSL https://dokploy.com/security/0.29.3.sh | bash
|
||||||
|
* Migration script: re-encrypt 2FA secrets after rotating BETTER_AUTH_SECRET.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* OLD_SECRET=<old_secret> NEW_SECRET=<new_secret> npx tsx apps/dokploy/scripts/migrate-auth-secret.ts
|
||||||
|
*
|
||||||
|
* Both OLD_SECRET and NEW_SECRET are required.
|
||||||
|
* Run this BEFORE restarting Dokploy with the new secret.
|
||||||
|
*/
|
||||||
|
import { db } from "@dokploy/server/db";
|
||||||
|
import { twoFactor } from "@dokploy/server/db/schema";
|
||||||
|
import { symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
const OLD_SECRET = process.env.OLD_SECRET as string;
|
||||||
|
const NEW_SECRET = process.env.NEW_SECRET as string;
|
||||||
|
|
||||||
|
if (!OLD_SECRET || !NEW_SECRET) {
|
||||||
|
console.error(
|
||||||
|
"❌ OLD_SECRET and NEW_SECRET environment variables are required.",
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
" Usage: OLD_SECRET=<old> NEW_SECRET=<new> npx tsx apps/dokploy/scripts/migrate-auth-secret.ts",
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OLD_SECRET === NEW_SECRET) {
|
||||||
|
console.error("❌ OLD_SECRET and NEW_SECRET must be different.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reEncrypt(
|
||||||
|
value: string,
|
||||||
|
oldSecret: string,
|
||||||
|
newSecret: string,
|
||||||
|
): Promise<string> {
|
||||||
|
const plaintext = await symmetricDecrypt({ key: oldSecret, data: value });
|
||||||
|
return symmetricEncrypt({ key: newSecret, data: plaintext });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log("🔍 Fetching 2FA records...");
|
||||||
|
const records = await db.select().from(twoFactor);
|
||||||
|
|
||||||
|
if (records.length === 0) {
|
||||||
|
console.log("✅ No 2FA records found, nothing to migrate.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📦 Found ${records.length} 2FA record(s) to migrate.`);
|
||||||
|
|
||||||
|
let migrated = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
await db.transaction(async (tx) => {
|
||||||
|
for (const record of records) {
|
||||||
|
try {
|
||||||
|
const [newSecret, newBackupCodes] = await Promise.all([
|
||||||
|
reEncrypt(record.secret, OLD_SECRET, NEW_SECRET),
|
||||||
|
reEncrypt(record.backupCodes, OLD_SECRET, NEW_SECRET),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await tx
|
||||||
|
.update(twoFactor)
|
||||||
|
.set({ secret: newSecret, backupCodes: newBackupCodes })
|
||||||
|
.where(eq(twoFactor.id, record.id));
|
||||||
|
|
||||||
|
migrated++;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(
|
||||||
|
`❌ Failed to migrate record ${record.id} (userId: ${record.userId}):`,
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
failed++;
|
||||||
|
throw err; // rollback the whole transaction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Migrated ${migrated} record(s) successfully.`);
|
||||||
|
|
||||||
|
if (failed > 0) {
|
||||||
|
console.error(
|
||||||
|
`❌ ${failed} record(s) failed — transaction was rolled back.`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("❌ Migration failed:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -25,8 +25,8 @@ import { findProjectById } from "@dokploy/server/services/project";
|
|||||||
import {
|
import {
|
||||||
getProviderHeaders,
|
getProviderHeaders,
|
||||||
getProviderName,
|
getProviderName,
|
||||||
selectAIProvider,
|
|
||||||
type Model,
|
type Model,
|
||||||
|
selectAIProvider,
|
||||||
} from "@dokploy/server/utils/ai/select-ai-provider";
|
} from "@dokploy/server/utils/ai/select-ai-provider";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { generateText } from "ai";
|
import { generateText } from "ai";
|
||||||
|
|||||||
@@ -640,7 +640,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
name: input.id,
|
name: input.id,
|
||||||
sourceType: "raw",
|
sourceType: "raw",
|
||||||
appName: appName,
|
appName: appName,
|
||||||
isolatedDeployment: true,
|
isolatedDeployment: template.config.config?.isolated !== false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await addNewService(ctx, compose.composeId);
|
await addNewService(ctx, compose.composeId);
|
||||||
@@ -700,11 +700,14 @@ export const composeRouter = createTRPCRouter({
|
|||||||
getTags: protectedProcedure
|
getTags: protectedProcedure
|
||||||
.input(z.object({ baseUrl: z.string().optional() }))
|
.input(z.object({ baseUrl: z.string().optional() }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const githubTemplates = await fetchTemplatesList(input.baseUrl);
|
try {
|
||||||
|
const githubTemplates = await fetchTemplatesList(input.baseUrl);
|
||||||
const allTags = githubTemplates.flatMap((template) => template.tags);
|
const allTags = githubTemplates.flatMap((template) => template.tags);
|
||||||
const uniqueTags = _.uniq(allTags);
|
return _.uniq(allTags);
|
||||||
return uniqueTags;
|
} catch (error) {
|
||||||
|
console.warn("Failed to fetch template tags:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
disconnectGitProvider: protectedProcedure
|
disconnectGitProvider: protectedProcedure
|
||||||
.input(apiFindCompose)
|
.input(apiFindCompose)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
findEnvironmentById,
|
findEnvironmentById,
|
||||||
findLibsqlById,
|
findLibsqlById,
|
||||||
findProjectById,
|
findProjectById,
|
||||||
|
getAccessibleServerIds,
|
||||||
getContainerLogs,
|
getContainerLogs,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
rebuildDatabase,
|
rebuildDatabase,
|
||||||
@@ -16,7 +17,6 @@ import {
|
|||||||
stopService,
|
stopService,
|
||||||
stopServiceRemote,
|
stopServiceRemote,
|
||||||
updateLibsqlById,
|
updateLibsqlById,
|
||||||
getAccessibleServerIds,
|
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import {
|
import {
|
||||||
addNewService,
|
addNewService,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
findEnvironmentById,
|
findEnvironmentById,
|
||||||
findMySqlById,
|
findMySqlById,
|
||||||
findProjectById,
|
findProjectById,
|
||||||
|
getAccessibleServerIds,
|
||||||
getContainerLogs,
|
getContainerLogs,
|
||||||
getServiceContainerCommand,
|
getServiceContainerCommand,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
@@ -20,7 +21,6 @@ import {
|
|||||||
stopService,
|
stopService,
|
||||||
stopServiceRemote,
|
stopServiceRemote,
|
||||||
updateMySqlById,
|
updateMySqlById,
|
||||||
getAccessibleServerIds,
|
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
findEnvironmentById,
|
findEnvironmentById,
|
||||||
findPostgresById,
|
findPostgresById,
|
||||||
findProjectById,
|
findProjectById,
|
||||||
|
getAccessibleServerIds,
|
||||||
getContainerLogs,
|
getContainerLogs,
|
||||||
getMountPath,
|
getMountPath,
|
||||||
getServiceContainerCommand,
|
getServiceContainerCommand,
|
||||||
@@ -21,7 +22,6 @@ import {
|
|||||||
stopService,
|
stopService,
|
||||||
stopServiceRemote,
|
stopServiceRemote,
|
||||||
updatePostgresById,
|
updatePostgresById,
|
||||||
getAccessibleServerIds,
|
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -856,8 +856,6 @@ export const projectRouter = createTRPCRouter({
|
|||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
).then((value) => value.environment);
|
).then((value) => value.environment);
|
||||||
|
|
||||||
console.log("targetProject", targetProject);
|
|
||||||
|
|
||||||
if (input.includeServices) {
|
if (input.includeServices) {
|
||||||
const servicesToDuplicate = input.selectedServices || [];
|
const servicesToDuplicate = input.selectedServices || [];
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
findEnvironmentById,
|
findEnvironmentById,
|
||||||
findProjectById,
|
findProjectById,
|
||||||
findRedisById,
|
findRedisById,
|
||||||
|
getAccessibleServerIds,
|
||||||
getContainerLogs,
|
getContainerLogs,
|
||||||
getServiceContainerCommand,
|
getServiceContainerCommand,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
@@ -19,7 +20,6 @@ import {
|
|||||||
stopService,
|
stopService,
|
||||||
stopServiceRemote,
|
stopServiceRemote,
|
||||||
updateRedisById,
|
updateRedisById,
|
||||||
getAccessibleServerIds,
|
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user