feat(gitea): add Gitea repository support

This commit is contained in:
Jason Parks
2025-03-17 15:17:35 -06:00
parent cf28640188
commit 9a11d0db97
101 changed files with 3272 additions and 2075 deletions

View File

@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import type { Schema } from "@dokploy/server/templates";
import type { CompleteTemplate } from "@dokploy/server/templates/processors";
import { processTemplate } from "@dokploy/server/templates/processors";
import type { Schema } from "@dokploy/server/templates";
import { describe, expect, it } from "vitest";
describe("processTemplate", () => {
// Mock schema for testing

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
@@ -32,7 +33,6 @@ import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
const ImportSchema = z.object({
base64: z.string(),

View File

@@ -4,10 +4,10 @@ import { Form } from "@/components/ui/form";
import { Secrets } from "@/components/ui/secrets";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { useEffect } from "react";
const addEnvironmentSchema = z.object({
env: z.string(),

View File

@@ -1,4 +1,6 @@
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
@@ -39,13 +41,11 @@ import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
import Link from "next/link";
const BitbucketProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),

View File

@@ -115,7 +115,11 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="Username" autoComplete="username" {...field} />
<Input
placeholder="Username"
autoComplete="username"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
@@ -130,7 +134,12 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input placeholder="Password" autoComplete="one-time-code" {...field} type="password" />
<Input
placeholder="Password"
autoComplete="one-time-code"
{...field}
type="password"
/>
</FormControl>
<FormMessage />
</FormItem>

View File

@@ -26,15 +26,15 @@ import {
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
import { useRouter } from "next/router";
import Link from "next/link";
import { useRouter } from "next/router";
import { GitIcon } from "@/components/icons/data-tools-icons";
import { Badge } from "@/components/ui/badge";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { GitIcon } from "@/components/icons/data-tools-icons";
const GitProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),

View File

@@ -235,7 +235,8 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
? "Loading...."
: field.value.owner
? repositories?.find(
(repo: GiteaRepository) => repo.name === field.value.repo,
(repo: GiteaRepository) =>
repo.name === field.value.repo,
)?.name
: "Select repository"}
@@ -327,7 +328,8 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
? "Loading...."
: field.value
? branches?.find(
(branch: GiteaBranch) => branch.name === field.value,
(branch: GiteaBranch) =>
branch.name === field.value,
)?.name
: "Select branch"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
@@ -411,4 +413,4 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
</Form>
</div>
);
};
};

View File

@@ -1,3 +1,5 @@
import { GithubIcon } from "@/components/icons/data-tools-icons";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
@@ -34,17 +36,15 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import Link from "next/link";
import { GithubIcon } from "@/components/icons/data-tools-icons";
const GithubProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),

View File

@@ -1,4 +1,6 @@
import { GitlabIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
@@ -35,17 +37,15 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import Link from "next/link";
import { GitlabIcon } from "@/components/icons/data-tools-icons";
const GitlabProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"),

View File

@@ -3,12 +3,12 @@ import { SaveGitProvider } from "@/components/dashboard/application/general/gene
import { SaveGiteaProvider } from "@/components/dashboard/application/general/generic/save-gitea-provider";
import { SaveGithubProvider } from "@/components/dashboard/application/general/generic/save-github-provider";
import {
BitbucketIcon,
DockerIcon,
GitIcon,
GiteaIcon,
GithubIcon,
GitlabIcon,
BitbucketIcon,
DockerIcon,
GitIcon,
GiteaIcon,
GithubIcon,
GitlabIcon,
} from "@/components/icons/data-tools-icons";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -20,190 +20,197 @@ import { SaveBitbucketProvider } from "./save-bitbucket-provider";
import { SaveDragNDrop } from "./save-drag-n-drop";
import { SaveGitlabProvider } from "./save-gitlab-provider";
type TabState = "github" | "docker" | "git" | "drop" | "gitlab" | "bitbucket" | "gitea";
type TabState =
| "github"
| "docker"
| "git"
| "drop"
| "gitlab"
| "bitbucket"
| "gitea";
interface Props {
applicationId: string;
applicationId: string;
}
export const ShowProviderForm = ({ applicationId }: Props) => {
const { data: githubProviders } = api.github.githubProviders.useQuery();
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
const { data: bitbucketProviders } =
api.bitbucket.bitbucketProviders.useQuery();
const { data: giteaProviders } = api.gitea.giteaProviders.useQuery();
const { data: githubProviders } = api.github.githubProviders.useQuery();
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
const { data: bitbucketProviders } =
api.bitbucket.bitbucketProviders.useQuery();
const { data: giteaProviders } = api.gitea.giteaProviders.useQuery();
const { data: application } = api.application.one.useQuery({ applicationId });
const [tab, setSab] = useState<TabState>(application?.sourceType || "github");
return (
<Card className="group relative w-full bg-transparent">
<CardHeader>
<CardTitle className="flex items-start justify-between">
<div className="flex flex-col gap-2">
<span className="flex flex-col space-y-0.5">Provider</span>
<p className="flex items-center text-sm font-normal text-muted-foreground">
Select the source of your code
</p>
</div>
<div className="hidden space-y-1 text-sm font-normal md:block">
<GitBranch className="size-6 text-muted-foreground" />
</div>
</CardTitle>
</CardHeader>
<CardContent>
<Tabs
value={tab}
className="w-full"
onValueChange={(e) => {
setSab(e as TabState);
}}
>
<div className="flex flex-row items-center justify-between w-full gap-4">
<TabsList className="md:grid md:w-fit md:grid-cols-7 max-md:overflow-x-scroll justify-start bg-transparent overflow-y-hidden">
<TabsTrigger
value="github"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<GithubIcon className="size-4 text-current fill-current" />
Github
</TabsTrigger>
<TabsTrigger
value="gitlab"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<GitlabIcon className="size-4 text-current fill-current" />
Gitlab
</TabsTrigger>
<TabsTrigger
value="bitbucket"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<BitbucketIcon className="size-4 text-current fill-current" />
Bitbucket
</TabsTrigger>
<TabsTrigger
value="gitea"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<GiteaIcon className="size-4 text-current fill-current" />
Gitea
</TabsTrigger>
<TabsTrigger
value="docker"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<DockerIcon className="size-5 text-current" />
Docker
</TabsTrigger>
<TabsTrigger
value="git"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<GitIcon />
Git
</TabsTrigger>
<TabsTrigger
value="drop"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<UploadCloud className="size-5 text-current" />
Drop
</TabsTrigger>
</TabsList>
</div>
const { data: application } = api.application.one.useQuery({ applicationId });
const [tab, setSab] = useState<TabState>(application?.sourceType || "github");
return (
<Card className="group relative w-full bg-transparent">
<CardHeader>
<CardTitle className="flex items-start justify-between">
<div className="flex flex-col gap-2">
<span className="flex flex-col space-y-0.5">Provider</span>
<p className="flex items-center text-sm font-normal text-muted-foreground">
Select the source of your code
</p>
</div>
<div className="hidden space-y-1 text-sm font-normal md:block">
<GitBranch className="size-6 text-muted-foreground" />
</div>
</CardTitle>
</CardHeader>
<CardContent>
<Tabs
value={tab}
className="w-full"
onValueChange={(e) => {
setSab(e as TabState);
}}
>
<div className="flex flex-row items-center justify-between w-full gap-4">
<TabsList className="md:grid md:w-fit md:grid-cols-7 max-md:overflow-x-scroll justify-start bg-transparent overflow-y-hidden">
<TabsTrigger
value="github"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<GithubIcon className="size-4 text-current fill-current" />
Github
</TabsTrigger>
<TabsTrigger
value="gitlab"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<GitlabIcon className="size-4 text-current fill-current" />
Gitlab
</TabsTrigger>
<TabsTrigger
value="bitbucket"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<BitbucketIcon className="size-4 text-current fill-current" />
Bitbucket
</TabsTrigger>
<TabsTrigger
value="gitea"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<GiteaIcon className="size-4 text-current fill-current" />
Gitea
</TabsTrigger>
<TabsTrigger
value="docker"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<DockerIcon className="size-5 text-current" />
Docker
</TabsTrigger>
<TabsTrigger
value="git"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<GitIcon />
Git
</TabsTrigger>
<TabsTrigger
value="drop"
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
<UploadCloud className="size-5 text-current" />
Drop
</TabsTrigger>
</TabsList>
</div>
<TabsContent value="github" className="w-full p-2">
{githubProviders && githubProviders?.length > 0 ? (
<SaveGithubProvider applicationId={applicationId} />
) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<GithubIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To deploy using GitHub, you need to configure your account
first. Please, go to{" "}
<Link
href="/dashboard/settings/git-providers"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
)}
</TabsContent>
<TabsContent value="gitlab" className="w-full p-2">
{gitlabProviders && gitlabProviders?.length > 0 ? (
<SaveGitlabProvider applicationId={applicationId} />
) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<GitlabIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To deploy using GitLab, you need to configure your account
first. Please, go to{" "}
<Link
href="/dashboard/settings/git-providers"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
)}
</TabsContent>
<TabsContent value="bitbucket" className="w-full p-2">
{bitbucketProviders && bitbucketProviders?.length > 0 ? (
<SaveBitbucketProvider applicationId={applicationId} />
) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<BitbucketIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To deploy using Bitbucket, you need to configure your account
first. Please, go to{" "}
<Link
href="/dashboard/settings/git-providers"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
)}
</TabsContent>
<TabsContent value="gitea" className="w-full p-2">
{giteaProviders && giteaProviders?.length > 0 ? (
<SaveGiteaProvider applicationId={applicationId} />
) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<GiteaIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To deploy using Gitea, you need to configure your account
first. Please, go to{" "}
<Link
href="/dashboard/settings/git-providers"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
)}
</TabsContent>
<TabsContent value="docker" className="w-full p-2">
<SaveDockerProvider applicationId={applicationId} />
</TabsContent>
<TabsContent value="github" className="w-full p-2">
{githubProviders && githubProviders?.length > 0 ? (
<SaveGithubProvider applicationId={applicationId} />
) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<GithubIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To deploy using GitHub, you need to configure your account
first. Please, go to{" "}
<Link
href="/dashboard/settings/git-providers"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
)}
</TabsContent>
<TabsContent value="gitlab" className="w-full p-2">
{gitlabProviders && gitlabProviders?.length > 0 ? (
<SaveGitlabProvider applicationId={applicationId} />
) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<GitlabIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To deploy using GitLab, you need to configure your account
first. Please, go to{" "}
<Link
href="/dashboard/settings/git-providers"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
)}
</TabsContent>
<TabsContent value="bitbucket" className="w-full p-2">
{bitbucketProviders && bitbucketProviders?.length > 0 ? (
<SaveBitbucketProvider applicationId={applicationId} />
) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<BitbucketIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To deploy using Bitbucket, you need to configure your account
first. Please, go to{" "}
<Link
href="/dashboard/settings/git-providers"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
)}
</TabsContent>
<TabsContent value="gitea" className="w-full p-2">
{giteaProviders && giteaProviders?.length > 0 ? (
<SaveGiteaProvider applicationId={applicationId} />
) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<GiteaIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground">
To deploy using Gitea, you need to configure your account
first. Please, go to{" "}
<Link
href="/dashboard/settings/git-providers"
className="text-foreground"
>
Settings
</Link>{" "}
to do so.
</span>
</div>
)}
</TabsContent>
<TabsContent value="docker" className="w-full p-2">
<SaveDockerProvider applicationId={applicationId} />
</TabsContent>
<TabsContent value="git" className="w-full p-2">
<SaveGitProvider applicationId={applicationId} />
</TabsContent>
<TabsContent value="drop" className="w-full p-2">
<SaveDragNDrop applicationId={applicationId} />
</TabsContent>
</Tabs>
</CardContent>
</Card>
);
};
<TabsContent value="git" className="w-full p-2">
<SaveGitProvider applicationId={applicationId} />
</TabsContent>
<TabsContent value="drop" className="w-full p-2">
<SaveDragNDrop applicationId={applicationId} />
</TabsContent>
</Tabs>
</CardContent>
</Card>
);
};

View File

@@ -10,8 +10,8 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,

View File

@@ -7,8 +7,8 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { Ban, CheckCircle2, Hammer, HelpCircle, Terminal } from "lucide-react";
import { useRouter } from "next/router";
import { toast } from "sonner";

View File

@@ -1,4 +1,6 @@
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
@@ -39,13 +41,11 @@ import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
import Link from "next/link";
const BitbucketProviderSchema = z.object({
composePath: z.string().min(1),

View File

@@ -1,3 +1,4 @@
import { GitIcon } from "@/components/icons/data-tools-icons";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
@@ -27,13 +28,12 @@ import {
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { KeyRoundIcon, LockIcon, X } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { GitIcon } from "@/components/icons/data-tools-icons";
import Link from "next/link";
const GitProviderSchema = z.object({
composePath: z.string().min(1),

View File

@@ -1,3 +1,4 @@
import { GithubIcon } from "@/components/icons/data-tools-icons";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
@@ -39,12 +40,11 @@ import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { GithubIcon } from "@/components/icons/data-tools-icons";
import Link from "next/link";
const GithubProviderSchema = z.object({
composePath: z.string().min(1),

View File

@@ -1,4 +1,6 @@
import { GitlabIcon } from "@/components/icons/data-tools-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
@@ -39,13 +41,11 @@ import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { GitlabIcon } from "@/components/icons/data-tools-icons";
import Link from "next/link";
const GitlabProviderSchema = z.object({
composePath: z.string().min(1),

View File

@@ -147,7 +147,9 @@ export const IsolatedDeployment = ({ composeId }: Props) => {
render={({ field }) => (
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>Enable Isolated Deployment ({data?.appName})</FormLabel>
<FormLabel>
Enable Isolated Deployment ({data?.appName})
</FormLabel>
<FormDescription>
Enable isolated deployment to the compose file.
</FormDescription>

View File

@@ -286,16 +286,21 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => {
<FormItem>
<FormLabel>Keep the latest</FormLabel>
<FormControl>
<Input type="number" placeholder={"keeps all the backups if left empty"} {...field} />
<Input
type="number"
placeholder={"keeps all the backups if left empty"}
{...field}
/>
</FormControl>
<FormDescription>
Optional. If provided, only keeps the latest N backups in the cloud.
Optional. If provided, only keeps the latest N backups
in the cloud.
</FormDescription>
<FormMessage />
</FormItem>
);
}}
/>
/>
<FormField
control={form.control}
name="enabled"

View File

@@ -16,18 +16,20 @@ import {
import { api } from "@/utils/api";
import { DatabaseBackup, Play, Trash2 } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { toast } from "sonner";
import type { ServiceType } from "../../application/advanced/show-resources";
import { AddBackup } from "./add-backup";
import { UpdateBackup } from "./update-backup";
import { useState } from "react";
interface Props {
id: string;
type: Exclude<ServiceType, "application" | "redis">;
}
export const ShowBackups = ({ id, type }: Props) => {
const [activeManualBackup, setActiveManualBackup] = useState<string | undefined>();
const [activeManualBackup, setActiveManualBackup] = useState<
string | undefined
>();
const queryMap = {
postgres: () =>
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
@@ -142,7 +144,7 @@ export const ShowBackups = ({ id, type }: Props) => {
<div className="flex flex-col gap-1">
<span className="font-medium">Keep Latest</span>
<span className="text-sm text-muted-foreground">
{backup.keepLatestCount || 'All'}
{backup.keepLatestCount || "All"}
</span>
</div>
</div>
@@ -153,7 +155,10 @@ export const ShowBackups = ({ id, type }: Props) => {
<Button
type="button"
variant="ghost"
isLoading={isManualBackup && activeManualBackup === backup.backupId}
isLoading={
isManualBackup &&
activeManualBackup === backup.backupId
}
onClick={async () => {
setActiveManualBackup(backup.backupId);
await manualBackup({

View File

@@ -92,7 +92,9 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
enabled: backup.enabled || false,
prefix: backup.prefix,
schedule: backup.schedule,
keepLatestCount: backup.keepLatestCount ? Number(backup.keepLatestCount) : undefined,
keepLatestCount: backup.keepLatestCount
? Number(backup.keepLatestCount)
: undefined,
});
}
}, [form, form.reset, backup]);
@@ -274,10 +276,15 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
<FormItem>
<FormLabel>Keep the latest</FormLabel>
<FormControl>
<Input type="number" placeholder={"keeps all the backups if left empty"} {...field} />
<Input
type="number"
placeholder={"keeps all the backups if left empty"}
{...field}
/>
</FormControl>
<FormDescription>
Optional. If provided, only keeps the latest N backups in the cloud.
Optional. If provided, only keeps the latest N backups
in the cloud.
</FormDescription>
<FormMessage />
</FormItem>

View File

@@ -1,6 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button";
import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -20,11 +20,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import Link from "next/link";
const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => {

View File

@@ -8,8 +8,8 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,

View File

@@ -1,6 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button";
import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -20,11 +20,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import Link from "next/link";
const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => {

View File

@@ -8,8 +8,8 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,

View File

@@ -1,6 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button";
import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -20,11 +20,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import Link from "next/link";
const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => {

View File

@@ -8,8 +8,8 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,

View File

@@ -1,6 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button";
import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -20,11 +20,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import Link from "next/link";
const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => {

View File

@@ -8,8 +8,8 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,

View File

@@ -67,7 +67,7 @@ import {
SearchIcon,
} from "lucide-react";
import Link from "next/link";
import { useState, useEffect } from "react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url";

View File

@@ -13,7 +13,6 @@ import {
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { useState } from "react";
const examples = [
"Make a personal blog",

View File

@@ -186,7 +186,9 @@ export const ShowProjects = () => {
target="_blank"
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
>
<span className="truncate">{domain.host}</span>
<span className="truncate">
{domain.host}
</span>
<ExternalLinkIcon className="size-4 shrink-0" />
</Link>
</DropdownMenuItem>
@@ -222,7 +224,9 @@ export const ShowProjects = () => {
target="_blank"
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
>
<span className="truncate">{domain.host}</span>
<span className="truncate">
{domain.host}
</span>
<ExternalLinkIcon className="size-4 shrink-0" />
</Link>
</DropdownMenuItem>

View File

@@ -1,6 +1,6 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button";
import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -20,11 +20,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import Link from "next/link";
const DockerProviderSchema = z.object({
externalPort: z.preprocess((a) => {

View File

@@ -8,8 +8,8 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,

View File

@@ -1,10 +1,10 @@
import { api } from "@/utils/api";
import {
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { api } from "@/utils/api";
import {
Area,
AreaChart,

View File

@@ -25,13 +25,13 @@ import {
import { type RouterOutputs, api } from "@/utils/api";
import { format } from "date-fns";
import {
ArrowDownUp,
AlertCircle,
InfoIcon,
ArrowDownUp,
Calendar as CalendarIcon,
InfoIcon,
} from "lucide-react";
import Link from "next/link";
import { useState, useEffect } from "react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { RequestDistributionChart } from "./request-distribution-chart";
import { RequestsTable } from "./requests-table";

View File

@@ -1,14 +1,22 @@
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { toast } from "sonner";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogDescription,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Select,
@@ -17,22 +25,14 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import copy from "copy-to-clipboard";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { toast } from "sonner";
import { z } from "zod";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
FormDescription,
} from "@/components/ui/form";
import { Switch } from "@/components/ui/switch";
import copy from "copy-to-clipboard";
import { CodeEditor } from "@/components/shared/code-editor";
const formSchema = z.object({
name: z.string().min(1, "Name is required"),

View File

@@ -1,3 +1,5 @@
import { DialogAction } from "@/components/shared/dialog-action";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -7,13 +9,11 @@ import {
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { ExternalLinkIcon, KeyIcon, Trash2, Clock, Tag } from "lucide-react";
import { formatDistanceToNow } from "date-fns";
import { Clock, ExternalLinkIcon, KeyIcon, Tag, Trash2 } from "lucide-react";
import Link from "next/link";
import { toast } from "sonner";
import { formatDistanceToNow } from "date-fns";
import { DialogAction } from "@/components/shared/dialog-action";
import { AddApiKey } from "./add-api-key";
import { Badge } from "@/components/ui/badge";
export const ShowApiKeys = () => {
const { data, refetch } = api.user.get.useQuery();

View File

@@ -35,9 +35,9 @@ import { api } from "@/utils/api";
import {
Boxes,
HelpCircle,
Loader2,
LockIcon,
MoreHorizontal,
Loader2,
} from "lucide-react";
import { toast } from "sonner";
import { AddNode } from "./add-node";

View File

@@ -45,7 +45,7 @@ const Schema = z.object({
redirectUri: z.string().min(1, {
message: "Redirect URI is required",
}),
organizationName: z.string().optional(), // Added organizationName to the schema
organizationName: z.string().optional(), // Added organizationName to the schema
});
type Schema = z.infer<typeof Schema>;
@@ -54,7 +54,6 @@ export const AddGiteaProvider = () => {
const utils = api.useUtils();
const [isOpen, setIsOpen] = useState(false);
const url = useUrl();
const { data: auth } = api.user.get.useQuery();
const { mutateAsync, error, isError } = api.gitea.create.useMutation(); // Updated API call for Gitea
const webhookUrl = `${url}/api/providers/gitea/callback`; // Updated webhook URL for Gitea
@@ -149,12 +148,15 @@ export const AddGiteaProvider = () => {
Redirect URI:{" "}
<span className="text-primary">{webhookUrl}</span>{" "}
</li>
<li>Select Permissions - organization: read, user: read, repository: read/write</li>
<li>
Select Permissions - organization: read, user: read,
repository: read/write
</li>
</ul>
</li>
<li>
After creating, you'll receive an ID and Secret,
copy them and paste them below.
After creating, you'll receive an ID and Secret, copy them
and paste them below.
</li>
</ol>
<FormField

View File

@@ -1,280 +1,303 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { useUrl } from "@/utils/hooks/use-url";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon } from "lucide-react";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { useRouter } from 'next/router';
const formSchema = z.object({
name: z.string().min(1, "Name is required"),
giteaUrl: z.string().min(1, "Gitea URL is required"),
clientId: z.string().min(1, "Client ID is required"),
clientSecret: z.string().min(1, "Client Secret is required"),
name: z.string().min(1, "Name is required"),
giteaUrl: z.string().min(1, "Gitea URL is required"),
clientId: z.string().min(1, "Client ID is required"),
clientSecret: z.string().min(1, "Client Secret is required"),
});
interface Props {
giteaId: string;
giteaId: string;
}
export const EditGiteaProvider = ({ giteaId }: Props) => {
const router = useRouter();
const [open, setOpen] = useState(false);
const { data: gitea, isLoading, refetch } = api.gitea.one.useQuery({ giteaId });
const { mutateAsync, isLoading: isUpdating } = api.gitea.update.useMutation();
const { mutateAsync: testConnection, isLoading: isTesting } = api.gitea.testConnection.useMutation();
const url = useUrl();
const utils = api.useUtils();
const router = useRouter();
const [open, setOpen] = useState(false);
const {
data: gitea,
isLoading,
refetch,
} = api.gitea.one.useQuery({ giteaId });
const { mutateAsync, isLoading: isUpdating } = api.gitea.update.useMutation();
const { mutateAsync: testConnection, isLoading: isTesting } =
api.gitea.testConnection.useMutation();
const url = useUrl();
const utils = api.useUtils();
// Handle OAuth redirect results
useEffect(() => {
const { connected, error } = router.query;
// Only process if router is ready and query parameters exist
if (!router.isReady) return;
// Handle OAuth redirect results
useEffect(() => {
const { connected, error } = router.query;
if (connected) {
toast.success("Successfully connected to Gitea", {
description: "Your Gitea provider has been authorized.",
id: 'gitea-connection-success'
});
refetch();
// Clear the query parameters to prevent re-triggering
router.replace({
pathname: router.pathname,
query: {}
}, undefined, { shallow: true });
}
// Only process if router is ready and query parameters exist
if (!router.isReady) return;
if (error) {
toast.error("Gitea Connection Failed", {
description: decodeURIComponent(error as string),
id: 'gitea-connection-error'
});
// Clear the query parameters to prevent re-triggering
router.replace({
pathname: router.pathname,
query: {}
}, undefined, { shallow: true });
}
}, [router.query, router.isReady, refetch]);
if (connected) {
toast.success("Successfully connected to Gitea", {
description: "Your Gitea provider has been authorized.",
id: "gitea-connection-success",
});
refetch();
// Clear the query parameters to prevent re-triggering
router.replace(
{
pathname: router.pathname,
query: {},
},
undefined,
{ shallow: true },
);
}
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
giteaUrl: "https://gitea.com",
clientId: "",
clientSecret: "",
},
});
if (error) {
toast.error("Gitea Connection Failed", {
description: decodeURIComponent(error as string),
id: "gitea-connection-error",
});
// Clear the query parameters to prevent re-triggering
router.replace(
{
pathname: router.pathname,
query: {},
},
undefined,
{ shallow: true },
);
}
}, [router.query, router.isReady, refetch]);
// Update form values when data is loaded
useEffect(() => {
if (gitea) {
form.reset({
name: gitea.gitProvider?.name || "",
giteaUrl: gitea.giteaUrl || "https://gitea.com",
clientId: gitea.clientId || "",
clientSecret: gitea.clientSecret || "",
});
}
}, [gitea, form.reset]);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
giteaUrl: "https://gitea.com",
clientId: "",
clientSecret: "",
},
});
const onSubmit = async (values: z.infer<typeof formSchema>) => {
await mutateAsync({
giteaId: giteaId,
gitProviderId: gitea?.gitProvider?.gitProviderId || "",
name: values.name,
giteaUrl: values.giteaUrl,
clientId: values.clientId,
clientSecret: values.clientSecret,
})
.then(async () => {
await utils.gitProvider.getAll.invalidate();
toast.success("Gitea provider updated successfully");
await refetch();
setOpen(false);
})
.catch(() => {
toast.error("Error updating Gitea provider");
});
};
// Update form values when data is loaded
useEffect(() => {
if (gitea) {
form.reset({
name: gitea.gitProvider?.name || "",
giteaUrl: gitea.giteaUrl || "https://gitea.com",
clientId: gitea.clientId || "",
clientSecret: gitea.clientSecret || "",
});
}
}, [gitea, form.reset]);
const handleTestConnection = async () => {
try {
const result = await testConnection({ giteaId });
toast.success("Gitea Connection Verified", {
description: result
});
} catch (error: any) {
const authUrl = error.authorizationUrl || getGiteaOAuthUrl();
toast.error("Gitea Not Connected", {
description: error.message || "Please complete the OAuth authorization process.",
action: authUrl && authUrl !== "#" ? {
label: "Authorize Now",
onClick: () => window.open(authUrl, "_blank")
} : undefined,
});
}
};
const onSubmit = async (values: z.infer<typeof formSchema>) => {
await mutateAsync({
giteaId: giteaId,
gitProviderId: gitea?.gitProvider?.gitProviderId || "",
name: values.name,
giteaUrl: values.giteaUrl,
clientId: values.clientId,
clientSecret: values.clientSecret,
})
.then(async () => {
await utils.gitProvider.getAll.invalidate();
toast.success("Gitea provider updated successfully");
await refetch();
setOpen(false);
})
.catch(() => {
toast.error("Error updating Gitea provider");
});
};
// Generate Gitea OAuth URL with state parameter
const getGiteaOAuthUrl = () => {
const clientId = form.getValues().clientId;
const giteaUrl = form.getValues().giteaUrl;
if (!clientId || !giteaUrl) {
toast.error("Configuration Incomplete", {
description: "Please fill in Client ID and Gitea URL first."
});
return "#";
}
const redirectUri = `${url}/api/providers/gitea/callback`;
// Use the scopes from the gitea data (if available), else fallback to default scopes
const scopes = gitea?.scopes?.split(',').join(' ') || 'repo repo:status read:user read:org';
//const scopes = gitea?.scopes || 'repo,repo:status,read:user,read:org';
const state = giteaId;
return `${giteaUrl}/login/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scopes)}&state=${encodeURIComponent(state)}`;
};
const handleTestConnection = async () => {
try {
const result = await testConnection({ giteaId });
toast.success("Gitea Connection Verified", {
description: result,
});
} catch (error: any) {
const authUrl = error.authorizationUrl || getGiteaOAuthUrl();
// Show loading state if data is being fetched
if (isLoading) {
return (
<Button variant="ghost" size="icon" disabled>
<PenBoxIcon className="h-4 w-4 text-muted-foreground" />
</Button>
);
}
toast.error("Gitea Not Connected", {
description:
error.message || "Please complete the OAuth authorization process.",
action:
authUrl && authUrl !== "#"
? {
label: "Authorize Now",
onClick: () => window.open(authUrl, "_blank"),
}
: undefined,
});
}
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="ghost" size="icon" className="group hover:bg-blue-500/10">
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Gitea Provider</DialogTitle>
<DialogDescription>
Update your Gitea provider details.
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="My Gitea" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="giteaUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Gitea URL</FormLabel>
<FormControl>
<Input placeholder="https://gitea.example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="clientId"
render={({ field }) => (
<FormItem>
<FormLabel>Client ID</FormLabel>
<FormControl>
<Input placeholder="Client ID" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="clientSecret"
render={({ field }) => (
<FormItem>
<FormLabel>Client Secret</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Client Secret"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
// Generate Gitea OAuth URL with state parameter
const getGiteaOAuthUrl = () => {
const clientId = form.getValues().clientId;
const giteaUrl = form.getValues().giteaUrl;
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={handleTestConnection}
isLoading={isTesting}
>
Test Connection
</Button>
<Button
type="button"
variant="outline"
onClick={() => {
const authUrl = getGiteaOAuthUrl();
if (authUrl !== "#") {
window.open(authUrl, "_blank");
}
}}
>
Connect to Gitea
</Button>
<Button type="submit" isLoading={isUpdating}>
Save
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
};
if (!clientId || !giteaUrl) {
toast.error("Configuration Incomplete", {
description: "Please fill in Client ID and Gitea URL first.",
});
return "#";
}
const redirectUri = `${url}/api/providers/gitea/callback`;
// Use the scopes from the gitea data (if available), else fallback to default scopes
const scopes =
gitea?.scopes?.split(",").join(" ") ||
"repo repo:status read:user read:org";
//const scopes = gitea?.scopes || 'repo,repo:status,read:user,read:org';
const state = giteaId;
return `${giteaUrl}/login/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scopes)}&state=${encodeURIComponent(state)}`;
};
// Show loading state if data is being fetched
if (isLoading) {
return (
<Button variant="ghost" size="icon" disabled>
<PenBoxIcon className="h-4 w-4 text-muted-foreground" />
</Button>
);
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10"
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Gitea Provider</DialogTitle>
<DialogDescription>
Update your Gitea provider details.
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="My Gitea" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="giteaUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Gitea URL</FormLabel>
<FormControl>
<Input placeholder="https://gitea.example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="clientId"
render={({ field }) => (
<FormItem>
<FormLabel>Client ID</FormLabel>
<FormControl>
<Input placeholder="Client ID" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="clientSecret"
render={({ field }) => (
<FormItem>
<FormLabel>Client Secret</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Client Secret"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={handleTestConnection}
isLoading={isTesting}
>
Test Connection
</Button>
<Button
type="button"
variant="outline"
onClick={() => {
const authUrl = getGiteaOAuthUrl();
if (authUrl !== "#") {
window.open(authUrl, "_blank");
}
}}
>
Connect to Gitea
</Button>
<Button type="submit" isLoading={isUpdating}>
Save
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,273 +1,285 @@
import {
BitbucketIcon,
GiteaIcon,
GithubIcon,
GitlabIcon,
GiteaIcon,
} from "@/components/icons/data-tools-icons";
import { DialogAction } from "@/components/shared/dialog-action";
import { Button, buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { api } from "@/utils/api";
import { useUrl } from "@/utils/hooks/use-url";
import { formatDate } from "date-fns";
import {
} from "@/components/icons/data-tools-icons";
import { DialogAction } from "@/components/shared/dialog-action";
import { Button, buttonVariants } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { useUrl } from "@/utils/hooks/use-url";
import { formatDate } from "date-fns";
import {
ExternalLinkIcon,
GitBranch,
ImportIcon,
Loader2,
Trash2,
} from "lucide-react";
import Link from "next/link";
import { toast } from "sonner";
import { AddBitbucketProvider } from "./bitbucket/add-bitbucket-provider";
import { EditBitbucketProvider } from "./bitbucket/edit-bitbucket-provider";
import { AddGithubProvider } from "./github/add-github-provider";
import { EditGithubProvider } from "./github/edit-github-provider";
import { AddGitlabProvider } from "./gitlab/add-gitlab-provider";
import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider";
import { AddGiteaProvider } from "./gitea/add-gitea-provider";
import { EditGiteaProvider } from "./gitea/edit-gitea-provider";
export const ShowGitProviders = () => {
const { data, isLoading, refetch } = api.gitProvider.getAll.useQuery();
const { mutateAsync, isLoading: isRemoving } = api.gitProvider.remove.useMutation();
const url = useUrl();
const getGitlabUrl = (
clientId: string,
gitlabId: string,
gitlabUrl: string,
) => {
const redirectUri = `${url}/api/providers/gitlab/callback?gitlabId=${gitlabId}`;
const scope = "api read_user read_repository";
const authUrl = `${gitlabUrl}/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`;
return authUrl;
};
const getGiteaUrl = (
clientId: string,
giteaId: string,
giteaUrl: string,
) => {
const redirectUri = `${url}/api/providers/gitea/callback?giteaId=${giteaId}`;
const scope = "repo";
const authUrl = `${giteaUrl}/login/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`;
return authUrl;
};
return (
<div className="w-full">
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto">
<div className="rounded-xl bg-background shadow-md ">
<CardHeader className="">
<CardTitle className="text-xl flex flex-row gap-2">
<GitBranch className="size-6 text-muted-foreground self-center" />
Git Providers
</CardTitle>
<CardDescription>
Connect your Git provider for authentication.
</CardDescription>
</CardHeader>
<CardContent className="space-y-2 py-8 border-t">
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground min-h-[25vh]">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />
</div>
) : (
<>
{data?.length === 0 ? (
<div className="flex flex-col items-center gap-3 min-h-[25vh] justify-center">
<GitBranch className="size-8 self-center text-muted-foreground" />
<span className="text-base text-muted-foreground text-center">
Create your first Git Provider
</span>
<div>
<div className="flex items-center bg-sidebar p-1 w-full rounded-lg">
<div className="flex items-center gap-4 p-3.5 rounded-lg bg-background border w-full">
<AddGithubProvider />
<AddGitlabProvider />
<AddBitbucketProvider />
<AddGiteaProvider />
</div>
</div>
</div>
</div>
) : (
<div className="flex flex-col gap-4 min-h-[25vh]">
<div className="flex flex-col gap-2 rounded-lg ">
<span className="text-base font-medium">
Available Providers
</span>
<div className="flex items-center bg-sidebar p-1 w-full rounded-lg">
<div className="flex items-center gap-4 p-3.5 rounded-lg bg-background border w-full">
<AddGithubProvider />
<AddGitlabProvider />
<AddBitbucketProvider />
<AddGiteaProvider />
</div>
</div>
</div>
<div className="flex flex-col gap-4 rounded-lg ">
{data?.map((gitProvider, _index) => {
const isGithub = gitProvider.providerType === "github";
const isGitlab = gitProvider.providerType === "gitlab";
const isBitbucket = gitProvider.providerType === "bitbucket";
const isGitea = gitProvider.providerType === "gitea";
const haveGithubRequirements = isGithub &&
gitProvider.github?.githubPrivateKey &&
gitProvider.github?.githubAppId &&
gitProvider.github?.githubInstallationId;
const haveGitlabRequirements = isGitlab &&
gitProvider.gitlab?.accessToken &&
gitProvider.gitlab?.refreshToken;
const haveGiteaRequirements = isGitea &&
gitProvider.gitea?.accessToken &&
gitProvider.gitea?.refreshToken;
return (
<div
key={gitProvider.gitProviderId}
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
>
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
<div className="flex flex-col items-center justify-between">
<div className="flex gap-2 flex-row items-center">
{isGithub && (
<GithubIcon className="size-5" />
)}
{isGitlab && (
<GitlabIcon className="size-5" />
)}
{isBitbucket && (
<BitbucketIcon className="size-5" />
)}
{isGitea && (
<GiteaIcon className="size-5" />
)}
<div className="flex flex-col gap-1">
<span className="text-sm font-medium">
{gitProvider.name}
</span>
<span className="text-xs text-muted-foreground">
{formatDate(
gitProvider.createdAt,
"yyyy-MM-dd hh:mm:ss a"
)}
</span>
</div>
</div>
</div>
<div className="flex flex-row gap-1">
{!haveGithubRequirements && isGithub && (
<div className="flex flex-col gap-1">
<Link
href={`${gitProvider?.github?.githubAppName}/installations/new?state=gh_setup:${gitProvider?.github.githubId}`}
className={buttonVariants({
size: "icon",
variant: "ghost",
})}
>
<ImportIcon className="size-4 text-primary" />
</Link>
</div>
)}
{haveGithubRequirements && isGithub && (
<div className="flex flex-col gap-1">
<Link
href={`${gitProvider?.github?.githubAppName}`}
target="_blank"
className={buttonVariants({
size: "icon",
variant: "ghost",
})}
>
<ExternalLinkIcon className="size-4 text-primary" />
</Link>
</div>
)}
{!haveGitlabRequirements && isGitlab && (
<div className="flex flex-col gap-1">
<Link
href={getGitlabUrl(
gitProvider.gitlab?.applicationId || "",
gitProvider.gitlab?.gitlabId || "",
gitProvider.gitlab?.gitlabUrl,
)}
target="_blank"
className={buttonVariants({
size: "icon",
variant: "ghost",
})}
>
<ImportIcon className="size-4 text-primary" />
</Link>
</div>
)}
{isGithub && haveGithubRequirements && (
<EditGithubProvider githubId={gitProvider.github?.githubId} />
)}
{isGitlab && (
<EditGitlabProvider gitlabId={gitProvider.gitlab?.gitlabId} />
)}
{isBitbucket && (
<EditBitbucketProvider bitbucketId={gitProvider.bitbucket?.bitbucketId} />
)}
} from "lucide-react";
import Link from "next/link";
import { toast } from "sonner";
import { AddBitbucketProvider } from "./bitbucket/add-bitbucket-provider";
import { EditBitbucketProvider } from "./bitbucket/edit-bitbucket-provider";
import { AddGiteaProvider } from "./gitea/add-gitea-provider";
import { EditGiteaProvider } from "./gitea/edit-gitea-provider";
import { AddGithubProvider } from "./github/add-github-provider";
import { EditGithubProvider } from "./github/edit-github-provider";
import { AddGitlabProvider } from "./gitlab/add-gitlab-provider";
import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider";
{isGitea && (
<EditGiteaProvider giteaId={gitProvider.gitea?.giteaId} />
)}
<DialogAction
title="Delete Git Provider"
description="Are you sure you want to delete this Git Provider?"
type="destructive"
onClick={async () => {
await mutateAsync({
gitProviderId: gitProvider.gitProviderId,
})
.then(() => {
toast.success("Git Provider deleted successfully");
refetch();
})
.catch(() => {
toast.error("Error deleting Git Provider");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10"
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
</div>
</div>
export const ShowGitProviders = () => {
const { data, isLoading, refetch } = api.gitProvider.getAll.useQuery();
const { mutateAsync, isLoading: isRemoving } =
api.gitProvider.remove.useMutation();
const url = useUrl();
const getGitlabUrl = (
clientId: string,
gitlabId: string,
gitlabUrl: string,
) => {
const redirectUri = `${url}/api/providers/gitlab/callback?gitlabId=${gitlabId}`;
const scope = "api read_user read_repository";
const authUrl = `${gitlabUrl}/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`;
return authUrl;
};
return (
<div className="w-full">
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto">
<div className="rounded-xl bg-background shadow-md ">
<CardHeader className="">
<CardTitle className="text-xl flex flex-row gap-2">
<GitBranch className="size-6 text-muted-foreground self-center" />
Git Providers
</CardTitle>
<CardDescription>
Connect your Git provider for authentication.
</CardDescription>
</CardHeader>
<CardContent className="space-y-2 py-8 border-t">
{isLoading ? (
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground min-h-[25vh]">
<span>Loading...</span>
<Loader2 className="animate-spin size-4" />
</div>
);
})}
</div>
<div className="flex flex-row gap-2 flex-wrap w-full justify-end mr-4">
{/* <AddCertificate /> */}
</div>
</div>
)}
</>
)}
</CardContent>
</div>
</Card>
</div>
) : (
<>
{data?.length === 0 ? (
<div className="flex flex-col items-center gap-3 min-h-[25vh] justify-center">
<GitBranch className="size-8 self-center text-muted-foreground" />
<span className="text-base text-muted-foreground text-center">
Create your first Git Provider
</span>
<div>
<div className="flex items-center bg-sidebar p-1 w-full rounded-lg">
<div className="flex items-center gap-4 p-3.5 rounded-lg bg-background border w-full">
<AddGithubProvider />
<AddGitlabProvider />
<AddBitbucketProvider />
<AddGiteaProvider />
</div>
</div>
</div>
</div>
) : (
<div className="flex flex-col gap-4 min-h-[25vh]">
<div className="flex flex-col gap-2 rounded-lg ">
<span className="text-base font-medium">
Available Providers
</span>
<div className="flex items-center bg-sidebar p-1 w-full rounded-lg">
<div className="flex items-center gap-4 p-3.5 rounded-lg bg-background border w-full">
<AddGithubProvider />
<AddGitlabProvider />
<AddBitbucketProvider />
<AddGiteaProvider />
</div>
</div>
</div>
<div className="flex flex-col gap-4 rounded-lg ">
{data?.map((gitProvider, _index) => {
const isGithub = gitProvider.providerType === "github";
const isGitlab = gitProvider.providerType === "gitlab";
const isBitbucket =
gitProvider.providerType === "bitbucket";
const isGitea = gitProvider.providerType === "gitea";
const haveGithubRequirements =
isGithub &&
gitProvider.github?.githubPrivateKey &&
gitProvider.github?.githubAppId &&
gitProvider.github?.githubInstallationId;
const haveGitlabRequirements =
isGitlab &&
gitProvider.gitlab?.accessToken &&
gitProvider.gitlab?.refreshToken;
const haveGiteaRequirements =
isGitea &&
gitProvider.gitea?.accessToken &&
gitProvider.gitea?.refreshToken;
return (
<div
key={gitProvider.gitProviderId}
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
>
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
<div className="flex flex-col items-center justify-between">
<div className="flex gap-2 flex-row items-center">
{isGithub && (
<GithubIcon className="size-5" />
)}
{isGitlab && (
<GitlabIcon className="size-5" />
)}
{isBitbucket && (
<BitbucketIcon className="size-5" />
)}
{isGitea && <GiteaIcon className="size-5" />}
<div className="flex flex-col gap-1">
<span className="text-sm font-medium">
{gitProvider.name}
</span>
<span className="text-xs text-muted-foreground">
{formatDate(
gitProvider.createdAt,
"yyyy-MM-dd hh:mm:ss a",
)}
</span>
</div>
</div>
</div>
<div className="flex flex-row gap-1">
{!haveGithubRequirements && isGithub && (
<div className="flex flex-col gap-1">
<Link
href={`${gitProvider?.github?.githubAppName}/installations/new?state=gh_setup:${gitProvider?.github.githubId}`}
className={buttonVariants({
size: "icon",
variant: "ghost",
})}
>
<ImportIcon className="size-4 text-primary" />
</Link>
</div>
)}
{haveGithubRequirements && isGithub && (
<div className="flex flex-col gap-1">
<Link
href={`${gitProvider?.github?.githubAppName}`}
target="_blank"
className={buttonVariants({
size: "icon",
variant: "ghost",
})}
>
<ExternalLinkIcon className="size-4 text-primary" />
</Link>
</div>
)}
{!haveGitlabRequirements && isGitlab && (
<div className="flex flex-col gap-1">
<Link
href={getGitlabUrl(
gitProvider.gitlab?.applicationId || "",
gitProvider.gitlab?.gitlabId || "",
gitProvider.gitlab?.gitlabUrl,
)}
target="_blank"
className={buttonVariants({
size: "icon",
variant: "ghost",
})}
>
<ImportIcon className="size-4 text-primary" />
</Link>
</div>
)}
{isGithub && haveGithubRequirements && (
<EditGithubProvider
githubId={gitProvider.github?.githubId}
/>
)}
{isGitlab && (
<EditGitlabProvider
gitlabId={gitProvider.gitlab?.gitlabId}
/>
)}
{isBitbucket && (
<EditBitbucketProvider
bitbucketId={
gitProvider.bitbucket?.bitbucketId
}
/>
)}
{isGitea && (
<EditGiteaProvider
giteaId={gitProvider.gitea?.giteaId}
/>
)}
<DialogAction
title="Delete Git Provider"
description="Are you sure you want to delete this Git Provider?"
type="destructive"
onClick={async () => {
await mutateAsync({
gitProviderId: gitProvider.gitProviderId,
})
.then(() => {
toast.success(
"Git Provider deleted successfully",
);
refetch();
})
.catch(() => {
toast.error(
"Error deleting Git Provider",
);
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10"
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
</div>
</div>
</div>
);
})}
</div>
<div className="flex flex-row gap-2 flex-wrap w-full justify-end mr-4">
{/* <AddCertificate /> */}
</div>
</div>
)}
</>
)}
</CardContent>
</div>
</Card>
</div>
);
};
};

View File

@@ -33,6 +33,7 @@ import { useTranslation } from "next-i18next";
import Link from "next/link";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal";
import { TerminalModal } from "../web-server/terminal-modal";
import { ShowServerActions } from "./actions/show-server-actions";
import { HandleServers } from "./handle-servers";
@@ -42,7 +43,6 @@ import { ShowMonitoringModal } from "./show-monitoring-modal";
import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal";
import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal";
import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription";
import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal";
export const ShowServers = () => {
const { t } = useTranslation("settings");

View File

@@ -1,3 +1,4 @@
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
@@ -22,7 +23,6 @@ import dynamic from "next/dynamic";
import type React from "react";
import { useEffect, useState } from "react";
import { badgeStateColor } from "../../application/logs/show";
import { Badge } from "@/components/ui/badge";
export const DockerLogsId = dynamic(
() =>

View File

@@ -12,7 +12,7 @@ import {
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { api } from "@/utils/api";
import { DatabaseIcon, AlertTriangle } from "lucide-react";
import { AlertTriangle, DatabaseIcon } from "lucide-react";
import { toast } from "sonner";
interface Props {

View File

@@ -1,6 +1,6 @@
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
import { ShowResources } from "@/components/dashboard/application/advanced/show-resources";
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
import { RebuildDatabase } from "./rebuild-database";
interface Props {

View File

@@ -241,34 +241,34 @@ export const BitbucketIcon = ({ className }: Props) => {
export const GiteaIcon = ({ className }: Props) => {
return (
<svg
className={className}
version="1.1"
id="main_outline"
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="5.67 143.05 628.65 387.55"
enableBackground="new 0 0 640 640"
className={className}
version="1.1"
id="main_outline"
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="5.67 143.05 628.65 387.55"
enableBackground="new 0 0 640 640"
>
<g>
<path
id="teabag"
style={{ fill: '#FFFFFF' }}
d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"
/>
<g>
<g>
<path
style={{ fill: '#609926' }}
d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"
/>
<path
style={{ fill: '#609926' }}
d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8C343.2,346.5,335,363.3,326.8,380.1z"
id="teabag"
style={{ fill: "#FFFFFF" }}
d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"
/>
<g>
<g>
<path
style={{ fill: "#609926" }}
d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"
/>
<path
style={{ fill: "#609926" }}
d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8C343.2,346.5,335,363.3,326.8,380.1z"
/>
</g>
</g>
</g>
</g>
</g>
</svg>
);
};

View File

@@ -1,10 +1,10 @@
import { api } from "@/utils/api";
import type { IUpdateData } from "@dokploy/server/index";
import { Download } from "lucide-react";
import { useRouter } from "next/router";
import { useEffect, useRef, useState } from "react";
import UpdateServer from "../dashboard/settings/web-server/update-server";
import { Button } from "../ui/button";
import { Download } from "lucide-react";
import {
Tooltip,
TooltipContent,

View File

@@ -37,7 +37,9 @@ export const BreadcrumbSidebar = ({ list }: Props) => {
)}
</BreadcrumbLink>
</BreadcrumbItem>
{_index + 1 < list.length && <BreadcrumbSeparator className="block" />}
{_index + 1 < list.length && (
<BreadcrumbSeparator className="block" />
)}
</Fragment>
))}
</BreadcrumbList>

View File

@@ -3,18 +3,18 @@ import { json } from "@codemirror/lang-json";
import { yaml } from "@codemirror/lang-yaml";
import { StreamLanguage } from "@codemirror/language";
import {
type Completion,
type CompletionContext,
type CompletionResult,
autocompletion,
} from "@codemirror/autocomplete";
import { properties } from "@codemirror/legacy-modes/mode/properties";
import { shell } from "@codemirror/legacy-modes/mode/shell";
import { EditorView } from "@codemirror/view";
import { githubDark, githubLight } from "@uiw/codemirror-theme-github";
import CodeMirror, { type ReactCodeMirrorProps } from "@uiw/react-codemirror";
import { useTheme } from "next-themes";
import {
autocompletion,
type CompletionContext,
type CompletionResult,
type Completion,
} from "@codemirror/autocomplete";
// Docker Compose completion options
const dockerComposeServices = [

View File

@@ -1,9 +1,9 @@
import type * as React from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import type * as React from "react";
import { DayPicker } from "react-day-picker";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export type CalendarProps = React.ComponentProps<typeof DayPicker>;

View File

@@ -0,0 +1,157 @@
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'gitea' AND
enumtypid = (SELECT oid FROM pg_type WHERE typname = 'sourceType')) THEN
ALTER TYPE "public"."sourceType" ADD VALUE 'gitea' BEFORE 'drop';
END IF;
END
$$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'gitea' AND
enumtypid = (SELECT oid FROM pg_type WHERE typname = 'sourceTypeCompose')) THEN
ALTER TYPE "public"."sourceTypeCompose" ADD VALUE 'gitea' BEFORE 'raw';
END IF;
END
$$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'gitea' AND
enumtypid = (SELECT oid FROM pg_type WHERE typname = 'gitProviderType')) THEN
ALTER TYPE "public"."gitProviderType" ADD VALUE 'gitea';
END IF;
END
$$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'gitea') THEN
CREATE TABLE "gitea" (
"giteaId" text PRIMARY KEY NOT NULL,
"giteaUrl" text DEFAULT 'https://gitea.com' NOT NULL,
"redirect_uri" text,
"client_id" text,
"client_secret" text,
"gitProviderId" text NOT NULL,
"gitea_username" text,
"access_token" text,
"refresh_token" text,
"expires_at" integer,
"scopes" text DEFAULT 'repo,repo:status,read:user,read:org',
"last_authenticated_at" integer
);
END IF;
END
$$;
--> statement-breakpoint
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaProjectId') THEN
ALTER TABLE "application" ADD COLUMN "giteaProjectId" integer;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaRepository') THEN
ALTER TABLE "application" ADD COLUMN "giteaRepository" text;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaOwner') THEN
ALTER TABLE "application" ADD COLUMN "giteaOwner" text;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaBranch') THEN
ALTER TABLE "application" ADD COLUMN "giteaBranch" text;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaBuildPath') THEN
ALTER TABLE "application" ADD COLUMN "giteaBuildPath" text DEFAULT '/';
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaPathNamespace') THEN
ALTER TABLE "application" ADD COLUMN "giteaPathNamespace" text;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'application' AND column_name = 'giteaId') THEN
ALTER TABLE "application" ADD COLUMN "giteaId" text;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'compose' AND column_name = 'giteaRepository') THEN
ALTER TABLE "compose" ADD COLUMN "giteaRepository" text;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'compose' AND column_name = 'giteaOwner') THEN
ALTER TABLE "compose" ADD COLUMN "giteaOwner" text;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'compose' AND column_name = 'giteaBranch') THEN
ALTER TABLE "compose" ADD COLUMN "giteaBranch" text;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'compose' AND column_name = 'giteaId') THEN
ALTER TABLE "compose" ADD COLUMN "giteaId" text;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'gitea_gitProviderId_git_provider_gitProviderId_fk'
) THEN
ALTER TABLE "gitea" ADD CONSTRAINT "gitea_gitProviderId_git_provider_gitProviderId_fk"
FOREIGN KEY ("gitProviderId") REFERENCES "public"."git_provider"("gitProviderId")
ON DELETE cascade ON UPDATE no action;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'application_giteaId_gitea_giteaId_fk'
) THEN
ALTER TABLE "application" ADD CONSTRAINT "application_giteaId_gitea_giteaId_fk"
FOREIGN KEY ("giteaId") REFERENCES "public"."gitea"("giteaId")
ON DELETE set null ON UPDATE no action;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'compose_giteaId_gitea_giteaId_fk'
) THEN
ALTER TABLE "compose" ADD CONSTRAINT "compose_giteaId_gitea_giteaId_fk"
FOREIGN KEY ("giteaId") REFERENCES "public"."gitea"("giteaId")
ON DELETE set null ON UPDATE no action;
END IF;
END $$;

View File

@@ -1,6 +1,6 @@
{
"id": "27ba0f3d-859f-4233-a179-8aee11ad9179",
"prevId": "dd51ff04-a160-4d0d-b72f-4916493b740f",
"id": "7c8508ba-01eb-487e-88cf-0abe10916904",
"prevId": "f74127dc-3cda-4091-988f-97b60eb22427",
"version": "7",
"dialect": "postgresql",
"tables": {
@@ -179,6 +179,13 @@
"notNull": true,
"default": "'github'"
},
"cleanCache": {
"name": "cleanCache",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"repository": {
"name": "repository",
"type": "text",
@@ -247,6 +254,43 @@
"primaryKey": false,
"notNull": false
},
"giteaProjectId": {
"name": "giteaProjectId",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"giteaRepository": {
"name": "giteaRepository",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaOwner": {
"name": "giteaOwner",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBranch": {
"name": "giteaBranch",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBuildPath": {
"name": "giteaBuildPath",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'/'"
},
"giteaPathNamespace": {
"name": "giteaPathNamespace",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketRepository": {
"name": "bitbucketRepository",
"type": "text",
@@ -458,6 +502,12 @@
"primaryKey": false,
"notNull": false
},
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketId": {
"name": "bitbucketId",
"type": "text",
@@ -538,6 +588,19 @@
"onDelete": "set null",
"onUpdate": "no action"
},
"application_giteaId_gitea_giteaId_fk": {
"name": "application_giteaId_gitea_giteaId_fk",
"tableFrom": "application",
"tableTo": "gitea",
"columnsFrom": [
"giteaId"
],
"columnsTo": [
"giteaId"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"application_bitbucketId_bitbucket_bitbucketId_fk": {
"name": "application_bitbucketId_bitbucket_bitbucketId_fk",
"tableFrom": "application",
@@ -1703,6 +1766,12 @@
"primaryKey": false,
"notNull": true
},
"keepLatestCount": {
"name": "keepLatestCount",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"databaseType": {
"name": "databaseType",
"type": "databaseType",
@@ -2908,6 +2977,24 @@
"primaryKey": false,
"notNull": false
},
"giteaRepository": {
"name": "giteaRepository",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaOwner": {
"name": "giteaOwner",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBranch": {
"name": "giteaBranch",
"type": "text",
"primaryKey": false,
"notNull": false
},
"customGitUrl": {
"name": "customGitUrl",
"type": "text",
@@ -3005,6 +3092,12 @@
"primaryKey": false,
"notNull": false
},
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"serverId": {
"name": "serverId",
"type": "text",
@@ -3079,6 +3172,19 @@
"onDelete": "set null",
"onUpdate": "no action"
},
"compose_giteaId_gitea_giteaId_fk": {
"name": "compose_giteaId_gitea_giteaId_fk",
"tableFrom": "compose",
"tableTo": "gitea",
"columnsFrom": [
"giteaId"
],
"columnsTo": [
"giteaId"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"compose_serverId_server_serverId_fk": {
"name": "compose_serverId_server_serverId_fk",
"tableFrom": "compose",
@@ -3937,6 +4043,107 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.gitea": {
"name": "gitea",
"schema": "",
"columns": {
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": true,
"notNull": true
},
"giteaUrl": {
"name": "giteaUrl",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'https://gitea.com'"
},
"redirect_uri": {
"name": "redirect_uri",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_id": {
"name": "client_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_secret": {
"name": "client_secret",
"type": "text",
"primaryKey": false,
"notNull": false
},
"gitProviderId": {
"name": "gitProviderId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"gitea_username": {
"name": "gitea_username",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"scopes": {
"name": "scopes",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'repo,repo:status,read:user,read:org'"
},
"last_authenticated_at": {
"name": "last_authenticated_at",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"gitea_gitProviderId_git_provider_gitProviderId_fk": {
"name": "gitea_gitProviderId_git_provider_gitProviderId_fk",
"tableFrom": "gitea",
"tableTo": "git_provider",
"columnsFrom": [
"gitProviderId"
],
"columnsTo": [
"gitProviderId"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.server": {
"name": "server",
"schema": "",
@@ -5001,6 +5208,7 @@
"github",
"gitlab",
"bitbucket",
"gitea",
"drop"
]
},
@@ -5097,6 +5305,7 @@
"github",
"gitlab",
"bitbucket",
"gitea",
"raw"
]
},
@@ -5125,7 +5334,8 @@
"values": [
"github",
"gitlab",
"bitbucket"
"bitbucket",
"gitea"
]
},
"public.serverStatus": {

View File

@@ -1,6 +1,6 @@
{
"id": "d4d95ab9-16bd-4583-949e-d6ae0a0bb7a0",
"prevId": "27ba0f3d-859f-4233-a179-8aee11ad9179",
"id": "c8f3ee9b-66c6-4a23-a151-78e04363bdf9",
"prevId": "f74127dc-3cda-4091-988f-97b60eb22427",
"version": "7",
"dialect": "postgresql",
"tables": {
@@ -179,6 +179,13 @@
"notNull": true,
"default": "'github'"
},
"cleanCache": {
"name": "cleanCache",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"repository": {
"name": "repository",
"type": "text",
@@ -247,6 +254,43 @@
"primaryKey": false,
"notNull": false
},
"giteaProjectId": {
"name": "giteaProjectId",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"giteaRepository": {
"name": "giteaRepository",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaOwner": {
"name": "giteaOwner",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBranch": {
"name": "giteaBranch",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBuildPath": {
"name": "giteaBuildPath",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'/'"
},
"giteaPathNamespace": {
"name": "giteaPathNamespace",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketRepository": {
"name": "bitbucketRepository",
"type": "text",
@@ -458,6 +502,12 @@
"primaryKey": false,
"notNull": false
},
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketId": {
"name": "bitbucketId",
"type": "text",
@@ -538,6 +588,19 @@
"onDelete": "set null",
"onUpdate": "no action"
},
"application_giteaId_gitea_giteaId_fk": {
"name": "application_giteaId_gitea_giteaId_fk",
"tableFrom": "application",
"tableTo": "gitea",
"columnsFrom": [
"giteaId"
],
"columnsTo": [
"giteaId"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"application_bitbucketId_bitbucket_bitbucketId_fk": {
"name": "application_bitbucketId_bitbucket_bitbucketId_fk",
"tableFrom": "application",
@@ -2914,6 +2977,24 @@
"primaryKey": false,
"notNull": false
},
"giteaRepository": {
"name": "giteaRepository",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaOwner": {
"name": "giteaOwner",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBranch": {
"name": "giteaBranch",
"type": "text",
"primaryKey": false,
"notNull": false
},
"customGitUrl": {
"name": "customGitUrl",
"type": "text",
@@ -3011,6 +3092,12 @@
"primaryKey": false,
"notNull": false
},
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"serverId": {
"name": "serverId",
"type": "text",
@@ -3085,6 +3172,19 @@
"onDelete": "set null",
"onUpdate": "no action"
},
"compose_giteaId_gitea_giteaId_fk": {
"name": "compose_giteaId_gitea_giteaId_fk",
"tableFrom": "compose",
"tableTo": "gitea",
"columnsFrom": [
"giteaId"
],
"columnsTo": [
"giteaId"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"compose_serverId_server_serverId_fk": {
"name": "compose_serverId_server_serverId_fk",
"tableFrom": "compose",
@@ -3943,6 +4043,107 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.gitea": {
"name": "gitea",
"schema": "",
"columns": {
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": true,
"notNull": true
},
"giteaUrl": {
"name": "giteaUrl",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'https://gitea.com'"
},
"redirect_uri": {
"name": "redirect_uri",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_id": {
"name": "client_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_secret": {
"name": "client_secret",
"type": "text",
"primaryKey": false,
"notNull": false
},
"gitProviderId": {
"name": "gitProviderId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"gitea_username": {
"name": "gitea_username",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"scopes": {
"name": "scopes",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'repo,repo:status,read:user,read:org'"
},
"last_authenticated_at": {
"name": "last_authenticated_at",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"gitea_gitProviderId_git_provider_gitProviderId_fk": {
"name": "gitea_gitProviderId_git_provider_gitProviderId_fk",
"tableFrom": "gitea",
"tableTo": "git_provider",
"columnsFrom": [
"gitProviderId"
],
"columnsTo": [
"gitProviderId"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.server": {
"name": "server",
"schema": "",
@@ -5007,6 +5208,7 @@
"github",
"gitlab",
"bitbucket",
"gitea",
"drop"
]
},
@@ -5103,6 +5305,7 @@
"github",
"gitlab",
"bitbucket",
"gitea",
"raw"
]
},
@@ -5131,7 +5334,8 @@
"values": [
"github",
"gitlab",
"bitbucket"
"bitbucket",
"gitea"
]
},
"public.serverStatus": {

View File

@@ -534,10 +534,31 @@
"tag": "0075_wild_xorn",
"breakpoints": true
},
{
"idx": 76,
"version": "7",
"when": 1742237840762,
"tag": "0076_tough_iron_patriot",
"breakpoints": true
},
{
"idx": 77,
"version": "7",
"when": 1742241730000,
"tag": "0076_young_sharon_ventura",
"breakpoints": true
},
{
"idx": 78,
"version": "7",
"when": 1742112194375,
"when": 1742241730001,
"tag": "0077_chemical_dreadnoughts",
"breakpoints": true
},
{
"idx": 79,
"version": "7",
"when": 17422417300002,
"tag": "0078_uneven_omega_sentinel",
"breakpoints": true
}

View File

@@ -0,0 +1,552 @@
{
"version": "5",
"dialect": "pg",
"entries": [
{
"idx": 0,
"version": "5",
"when": 1713262741218,
"tag": "0000_reflective_puck",
"breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1713761637676,
"tag": "0001_striped_tattoo",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1713763492341,
"tag": "0002_ambiguous_carlie_cooper",
"breakpoints": true
},
{
"idx": 3,
"version": "5",
"when": 1713947141424,
"tag": "0003_square_lightspeed",
"breakpoints": true
},
{
"idx": 4,
"version": "5",
"when": 1714004732716,
"tag": "0004_nice_tenebrous",
"breakpoints": true
},
{
"idx": 5,
"version": "5",
"when": 1715551130605,
"tag": "0005_cute_terror",
"breakpoints": true
},
{
"idx": 6,
"version": "6",
"when": 1715563165991,
"tag": "0006_oval_jimmy_woo",
"breakpoints": true
},
{
"idx": 7,
"version": "6",
"when": 1715563497100,
"tag": "0007_cute_guardsmen",
"breakpoints": true
},
{
"idx": 8,
"version": "6",
"when": 1715564143641,
"tag": "0008_lazy_sage",
"breakpoints": true
},
{
"idx": 9,
"version": "6",
"when": 1715564774423,
"tag": "0009_majestic_spencer_smythe",
"breakpoints": true
},
{
"idx": 10,
"version": "6",
"when": 1715574037832,
"tag": "0010_lean_black_widow",
"breakpoints": true
},
{
"idx": 11,
"version": "6",
"when": 1715574230599,
"tag": "0011_petite_calypso",
"breakpoints": true
},
{
"idx": 12,
"version": "6",
"when": 1716015716708,
"tag": "0012_chubby_umar",
"breakpoints": true
},
{
"idx": 13,
"version": "6",
"when": 1716076179443,
"tag": "0013_blushing_starjammers",
"breakpoints": true
},
{
"idx": 14,
"version": "6",
"when": 1716715367982,
"tag": "0014_same_hammerhead",
"breakpoints": true
},
{
"idx": 15,
"version": "6",
"when": 1717564517104,
"tag": "0015_fearless_callisto",
"breakpoints": true
},
{
"idx": 16,
"version": "6",
"when": 1719109196484,
"tag": "0016_chunky_leopardon",
"breakpoints": true
},
{
"idx": 17,
"version": "6",
"when": 1719547174326,
"tag": "0017_minor_post",
"breakpoints": true
},
{
"idx": 18,
"version": "6",
"when": 1719928377858,
"tag": "0018_careful_killmonger",
"breakpoints": true
},
{
"idx": 19,
"version": "6",
"when": 1721110706912,
"tag": "0019_heavy_freak",
"breakpoints": true
},
{
"idx": 20,
"version": "6",
"when": 1721363861686,
"tag": "0020_fantastic_slapstick",
"breakpoints": true
},
{
"idx": 21,
"version": "6",
"when": 1721370423752,
"tag": "0021_premium_sebastian_shaw",
"breakpoints": true
},
{
"idx": 22,
"version": "6",
"when": 1721531163852,
"tag": "0022_warm_colonel_america",
"breakpoints": true
},
{
"idx": 23,
"version": "6",
"when": 1721542782659,
"tag": "0023_icy_maverick",
"breakpoints": true
},
{
"idx": 24,
"version": "6",
"when": 1721603595092,
"tag": "0024_dapper_supernaut",
"breakpoints": true
},
{
"idx": 25,
"version": "6",
"when": 1721633853118,
"tag": "0025_lying_mephisto",
"breakpoints": true
},
{
"idx": 26,
"version": "6",
"when": 1721979220929,
"tag": "0026_known_dormammu",
"breakpoints": true
},
{
"idx": 27,
"version": "6",
"when": 1722445099203,
"tag": "0027_red_lady_bullseye",
"breakpoints": true
},
{
"idx": 28,
"version": "6",
"when": 1722503439951,
"tag": "0028_jittery_eternity",
"breakpoints": true
},
{
"idx": 29,
"version": "6",
"when": 1722578386823,
"tag": "0029_colossal_zodiak",
"breakpoints": true
},
{
"idx": 30,
"version": "6",
"when": 1723608499147,
"tag": "0030_little_kabuki",
"breakpoints": true
},
{
"idx": 31,
"version": "6",
"when": 1723701656243,
"tag": "0031_steep_vulture",
"breakpoints": true
},
{
"idx": 32,
"version": "6",
"when": 1723705257806,
"tag": "0032_flashy_shadow_king",
"breakpoints": true
},
{
"idx": 33,
"version": "6",
"when": 1725250322137,
"tag": "0033_white_hawkeye",
"breakpoints": true
},
{
"idx": 34,
"version": "6",
"when": 1725256397019,
"tag": "0034_aspiring_secret_warriors",
"breakpoints": true
},
{
"idx": 35,
"version": "6",
"when": 1725429324584,
"tag": "0035_cool_gravity",
"breakpoints": true
},
{
"idx": 36,
"version": "6",
"when": 1725519351871,
"tag": "0036_tired_ronan",
"breakpoints": true
},
{
"idx": 37,
"version": "6",
"when": 1726988289562,
"tag": "0037_legal_namor",
"breakpoints": true
},
{
"idx": 38,
"version": "6",
"when": 1727942090102,
"tag": "0038_rapid_landau",
"breakpoints": true
},
{
"idx": 39,
"version": "6",
"when": 1728021127765,
"tag": "0039_many_tiger_shark",
"breakpoints": true
},
{
"idx": 40,
"version": "6",
"when": 1728780577084,
"tag": "0040_graceful_wolfsbane",
"breakpoints": true
},
{
"idx": 41,
"version": "6",
"when": 1729667438853,
"tag": "0041_huge_bruce_banner",
"breakpoints": true
},
{
"idx": 42,
"version": "6",
"when": 1729984439862,
"tag": "0042_fancy_havok",
"breakpoints": true
},
{
"idx": 43,
"version": "6",
"when": 1731873965888,
"tag": "0043_closed_naoko",
"breakpoints": true
},
{
"idx": 44,
"version": "6",
"when": 1731875539532,
"tag": "0044_sour_true_believers",
"breakpoints": true
},
{
"idx": 45,
"version": "6",
"when": 1732644181718,
"tag": "0045_smiling_blur",
"breakpoints": true
},
{
"idx": 46,
"version": "6",
"when": 1732851191048,
"tag": "0046_purple_sleeper",
"breakpoints": true
},
{
"idx": 47,
"version": "6",
"when": 1733599090582,
"tag": "0047_tidy_revanche",
"breakpoints": true
},
{
"idx": 48,
"version": "6",
"when": 1733599163710,
"tag": "0048_flat_expediter",
"breakpoints": true
},
{
"idx": 49,
"version": "6",
"when": 1733628762978,
"tag": "0049_dark_leopardon",
"breakpoints": true
},
{
"idx": 50,
"version": "6",
"when": 1733889104203,
"tag": "0050_nappy_wrecker",
"breakpoints": true
},
{
"idx": 51,
"version": "6",
"when": 1734241482851,
"tag": "0051_hard_gorgon",
"breakpoints": true
},
{
"idx": 52,
"version": "6",
"when": 1734809337308,
"tag": "0052_bumpy_luckman",
"breakpoints": true
},
{
"idx": 53,
"version": "6",
"when": 1735118844878,
"tag": "0053_broken_kulan_gath",
"breakpoints": true
},
{
"idx": 54,
"version": "6",
"when": 1736669421560,
"tag": "0054_nervous_spencer_smythe",
"breakpoints": true
},
{
"idx": 55,
"version": "6",
"when": 1736669623831,
"tag": "0055_next_serpent_society",
"breakpoints": true
},
{
"idx": 56,
"version": "6",
"when": 1736789918294,
"tag": "0056_majestic_skaar",
"breakpoints": true
},
{
"idx": 57,
"version": "6",
"when": 1737306063563,
"tag": "0057_tricky_living_tribunal",
"breakpoints": true
},
{
"idx": 58,
"version": "6",
"when": 1737612903012,
"tag": "0058_brown_sharon_carter",
"breakpoints": true
},
{
"idx": 59,
"version": "6",
"when": 1737615160768,
"tag": "0059_striped_bill_hollister",
"breakpoints": true
},
{
"idx": 60,
"version": "6",
"when": 1737929896838,
"tag": "0060_disable-aggressive-cache",
"breakpoints": true
},
{
"idx": 61,
"version": "7",
"when": 1738481304953,
"tag": "0061_many_molten_man",
"breakpoints": true
},
{
"idx": 62,
"version": "7",
"when": 1738482795112,
"tag": "0062_slippery_white_tiger",
"breakpoints": true
},
{
"idx": 63,
"version": "7",
"when": 1738522845992,
"tag": "0063_panoramic_dreadnoughts",
"breakpoints": true
},
{
"idx": 64,
"version": "7",
"when": 1738564387043,
"tag": "0064_previous_agent_brand",
"breakpoints": true
},
{
"idx": 65,
"version": "7",
"when": 1739087857244,
"tag": "0065_daily_zaladane",
"breakpoints": true
},
{
"idx": 66,
"version": "7",
"when": 1739426913392,
"tag": "0066_yielding_echo",
"breakpoints": true
},
{
"idx": 67,
"version": "7",
"when": 1740892043121,
"tag": "0067_condemned_sugar_man",
"breakpoints": true
},
{
"idx": 68,
"version": "7",
"when": 1740897756774,
"tag": "0068_complex_rhino",
"breakpoints": true
},
{
"idx": 69,
"version": "7",
"when": 1741152916611,
"tag": "0069_legal_bill_hollister",
"breakpoints": true
},
{
"idx": 70,
"version": "7",
"when": 1741322697251,
"tag": "0070_useful_serpent_society",
"breakpoints": true
},
{
"idx": 71,
"version": "7",
"when": 1741559743256,
"tag": "0071_flimsy_plazm",
"breakpoints": true
},
{
"idx": 72,
"version": "7",
"when": 1741593124105,
"tag": "0072_low_redwing",
"breakpoints": true
},
{
"idx": 73,
"version": "7",
"when": 1741645208694,
"tag": "0073_dark_tigra",
"breakpoints": true
},
{
"idx": 74,
"version": "7",
"when": 1741673569715,
"tag": "0074_military_miss_america",
"breakpoints": true
},
{
"idx": 75,
"version": "7",
"when": 1742018928109,
"tag": "0075_wild_xorn",
"breakpoints": true
},
{
"idx": 76,
"version": "7",
"when": 1742237840762,
"tag": "0076_tough_iron_patriot",
"breakpoints": true
},
{
"idx": 77,
"version": "7",
"when": 1742238314349,
"tag": "0077_mature_tomorrow_man",
"breakpoints": true
}
]
}

View File

@@ -5,23 +5,23 @@
/** @type {import("next").NextConfig} */
const nextConfig = {
reactStrictMode: true,
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
transpilePackages: ["@dokploy/server"],
/**
* If you are using `appDir` then you must comment the below `i18n` config out.
*
* @see https://github.com/vercel/next.js/issues/41980
*/
i18n: {
locales: ["en"],
defaultLocale: "en",
},
reactStrictMode: true,
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
transpilePackages: ["@dokploy/server"],
/**
* If you are using `appDir` then you must comment the below `i18n` config out.
*
* @see https://github.com/vercel/next.js/issues/41980
*/
i18n: {
locales: ["en"],
defaultLocale: "en",
},
};
export default nextConfig;

View File

@@ -84,7 +84,7 @@
"@trpc/client": "^10.43.6",
"@trpc/next": "^10.43.6",
"@trpc/react-query": "^10.43.6",
"@trpc/server": "^10.43.6",
"@trpc/server": "^10.45.2",
"@uiw/codemirror-theme-github": "^4.22.1",
"@uiw/react-codemirror": "^4.22.1",
"@xterm/addon-attach": "0.10.0",
@@ -162,7 +162,7 @@
"@types/js-yaml": "4.0.9",
"@types/lodash": "4.17.4",
"@types/micromatch": "4.0.9",
"@types/node": "^18.17.0",
"@types/node": "^18.19.42",
"@types/node-os-utils": "1.3.4",
"@types/node-schedule": "2.1.6",
"@types/nodemailer": "^6.4.15",
@@ -197,8 +197,6 @@
]
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
"extends": ["@commitlint/config-conventional"]
}
}

View File

@@ -112,13 +112,6 @@ export default async function handler(
res.status(301).json({ message: "Branch Not Match" });
return;
}
<<<<<<< HEAD
} else if (sourceType === "gitea") {
const branchName = extractBranchName(req.headers, req.body);
if (!branchName || branchName !== application.giteaBranch) {
res.status(301).json({ message: "Branch Not Match" });
return;
=======
const commitedPaths = await extractCommitedPaths(
req.body,
@@ -134,7 +127,12 @@ export default async function handler(
if (!shouldDeployPaths) {
res.status(301).json({ message: "Watch Paths Not Match" });
return;
>>>>>>> fork/canary
}
} else if (sourceType === "gitea") {
const branchName = extractBranchName(req.headers, req.body);
if (!branchName || branchName !== application.giteaBranch) {
res.status(301).json({ message: "Branch Not Match" });
return;
}
}

View File

@@ -8,8 +8,8 @@ import { eq } from "drizzle-orm";
import type { NextApiRequest, NextApiResponse } from "next";
import {
extractBranchName,
extractCommitedPaths,
extractCommitMessage,
extractCommitedPaths,
extractHash,
} from "../[refreshToken]";

View File

@@ -1,52 +1,41 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { findGiteaById } from '@dokploy/server';
import type { NextApiRequest, NextApiResponse } from "next";
import { findGitea, redirectWithError } from "./helper";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
req: NextApiRequest,
res: NextApiResponse,
) {
try {
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
if (req.method !== "GET") {
return res.status(405).json({ error: "Method not allowed" });
}
const { giteaId } = req.query;
const { giteaId } = req.query;
if (!giteaId || Array.isArray(giteaId)) {
return res.status(400).json({ error: 'Invalid Gitea provider ID' });
}
if (!giteaId || Array.isArray(giteaId)) {
return res.status(400).json({ error: "Invalid Gitea provider ID" });
}
let gitea;
try {
gitea = await findGiteaById(giteaId);
} catch (findError) {
console.error('Error finding Gitea provider:', findError);
return res.status(404).json({ error: 'Failed to find Gitea provider' });
}
const gitea = await findGitea(giteaId as string);
if (!gitea || !gitea.clientId || !gitea.redirectUri) {
return redirectWithError(res, "Incomplete OAuth configuration");
}
if (!gitea.clientId || !gitea.redirectUri) {
return res.status(400).json({
error: 'Incomplete OAuth configuration',
missingClientId: !gitea.clientId,
missingRedirectUri: !gitea.redirectUri
});
}
// Generate the Gitea authorization URL
const authorizationUrl = new URL(`${gitea.giteaUrl}/login/oauth/authorize`);
authorizationUrl.searchParams.append("client_id", gitea.clientId as string);
authorizationUrl.searchParams.append("response_type", "code");
authorizationUrl.searchParams.append(
"redirect_uri",
gitea.redirectUri as string,
);
authorizationUrl.searchParams.append("scope", "read:user repo");
authorizationUrl.searchParams.append("state", giteaId as string);
// Use the state parameter to pass the giteaId
// This is more secure than adding it to the redirect URI
const state = giteaId;
const authorizationUrl = new URL(`${gitea.giteaUrl}/login/oauth/authorize`);
authorizationUrl.searchParams.append('client_id', gitea.clientId);
authorizationUrl.searchParams.append('response_type', 'code');
authorizationUrl.searchParams.append('redirect_uri', gitea.redirectUri);
authorizationUrl.searchParams.append('scope', 'read:user repo');
authorizationUrl.searchParams.append('state', state);
// Redirect the user to the Gitea authorization page
res.redirect(307, authorizationUrl.toString());
} catch (error) {
console.error('Error initiating Gitea OAuth flow:', error);
return res.status(500).json({ error: 'Internal server error' });
}
}
// Redirect user to Gitea authorization URL
return res.redirect(307, authorizationUrl.toString());
} catch (error) {
console.error("Error initiating Gitea OAuth flow:", error);
return res.status(500).json({ error: "Internal server error" });
}
}

View File

@@ -1,186 +1,94 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { findGiteaById, updateGitea } from '@dokploy/server';
import { updateGitea } from "@dokploy/server";
import type { NextApiRequest, NextApiResponse } from "next";
import { type Gitea, findGitea, redirectWithError } from "./helper";
// Helper to parse the state parameter
const parseState = (state: string): string | null => {
try {
const stateObj =
state.startsWith("{") && state.endsWith("}") ? JSON.parse(state) : {};
return stateObj.giteaId || state || null;
} catch {
return null;
}
};
// Helper to fetch access token from Gitea
const fetchAccessToken = async (gitea: Gitea, code: string) => {
const response = await fetch(`${gitea.giteaUrl}/login/oauth/access_token`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams({
client_id: gitea.clientId as string,
client_secret: gitea.clientSecret as string,
code,
grant_type: "authorization_code",
redirect_uri: gitea.redirectUri || "",
}),
});
const responseText = await response.text();
return response.ok
? JSON.parse(responseText)
: { error: "Token exchange failed", responseText };
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
req: NextApiRequest,
res: NextApiResponse,
) {
try {
console.log('Full Callback Request:', {
query: req.query,
method: req.method,
timestamp: new Date().toISOString()
});
const { code, state } = req.query;
const { code, state } = req.query;
if (!code || Array.isArray(code) || !state || Array.isArray(state)) {
return redirectWithError(
res,
"Invalid authorization code or state parameter",
);
}
// Verify received parameters
console.log('Received Parameters:', {
code: code ? 'Present' : 'Missing',
state: state ? 'Present' : 'Missing'
});
const giteaId = parseState(state as string);
if (!giteaId) return redirectWithError(res, "Invalid state format");
if (!code || Array.isArray(code)) {
console.error('Invalid code:', code);
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Invalid authorization code')}`
);
}
const gitea = await findGitea(giteaId);
if (!gitea) return redirectWithError(res, "Failed to find Gitea provider");
// The state parameter now contains the giteaId
if (!state || Array.isArray(state)) {
console.error('Invalid state parameter:', state);
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Invalid state parameter')}`
);
}
// Fetch the access token from Gitea
const result = await fetchAccessToken(gitea, code as string);
// Extract the giteaId from the state parameter
let giteaId: string;
try {
// The state could be a simple string or a JSON object
if (state.startsWith('{') && state.endsWith('}')) {
const stateObj = JSON.parse(state);
giteaId = stateObj.giteaId;
} else {
giteaId = state;
}
if (result.error) {
console.error("Token exchange failed:", result);
return redirectWithError(res, result.error);
}
if (!giteaId) {
throw new Error('giteaId not found in state parameter');
}
} catch (parseError) {
console.error('Error parsing state parameter:', parseError);
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Invalid state format')}`
);
}
if (!result.access_token) {
console.error("Missing access token:", result);
return redirectWithError(res, "No access token received");
}
let gitea;
try {
gitea = await findGiteaById(giteaId);
} catch (findError) {
console.error('Error finding Gitea provider:', findError);
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Failed to find Gitea provider')}`
);
}
const expiresAt = result.expires_in
? Math.floor(Date.now() / 1000) + result.expires_in
: null;
// Extensive logging of Gitea provider details
console.log('Gitea Provider Details:', {
id: gitea.giteaId,
url: gitea.giteaUrl,
clientId: gitea.clientId ? 'Present' : 'Missing',
clientSecret: gitea.clientSecret ? 'Present' : 'Missing',
redirectUri: gitea.redirectUri
});
try {
const updatedGitea = await updateGitea(gitea.giteaId, {
accessToken: result.access_token,
refreshToken: result.refresh_token,
expiresAt,
...(result.organizationName
? { organizationName: result.organizationName }
: {}),
});
// Validate required OAuth parameters
if (!gitea.clientId || !gitea.clientSecret) {
console.error('Missing OAuth configuration:', {
hasClientId: !!gitea.clientId,
hasClientSecret: !!gitea.clientSecret
});
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Incomplete OAuth configuration')}`
);
}
const response = await fetch(`${gitea.giteaUrl}/login/oauth/access_token`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
},
body: new URLSearchParams({
client_id: gitea.clientId as string,
client_secret: gitea.clientSecret as string,
code: code as string,
grant_type: "authorization_code",
redirect_uri: gitea.redirectUri || '',
}),
});
// Log raw response details
const responseText = await response.text();
let result;
try {
result = JSON.parse(responseText);
} catch (parseError) {
console.error('Failed to parse response:', {
error: parseError,
responseText
});
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Failed to parse token response')}`
);
}
if (!response.ok) {
console.error('Gitea token exchange failed:', {
result,
responseStatus: response.status
});
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent(result.error || 'Token exchange failed')}`
);
}
// Validate token response
if (!result.access_token) {
console.error('Missing access token in response:', {
fullResponse: result
});
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('No access token received')}`
);
}
const expiresAt = result.expires_in
? Math.floor(Date.now() / 1000) + result.expires_in
: null;
try {
// Perform the update
const updatedGitea = await updateGitea(gitea.giteaId, {
accessToken: result.access_token,
refreshToken: result.refresh_token,
expiresAt,
...(result.organizationName ? { organizationName: result.organizationName } : {}),
});
// Log successful update
console.log('Gitea provider updated successfully:', {
hasAccessToken: !!updatedGitea.accessToken,
hasRefreshToken: !!updatedGitea.refreshToken,
expiresAt: updatedGitea.expiresAt
});
return res.redirect(307, "/dashboard/settings/git-providers?connected=true");
} catch (updateError) {
console.error('Failed to update Gitea provider:', {
error: updateError,
giteaId: gitea.giteaId
});
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Failed to store access token')}`
);
}
} catch (error) {
console.error('Comprehensive Callback Error:', error);
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Internal server error')}`
);
}
}
console.log("Gitea provider updated successfully:", updatedGitea);
return res.redirect(
307,
"/dashboard/settings/git-providers?connected=true",
);
} catch (updateError) {
console.error("Failed to update Gitea provider:", updateError);
return redirectWithError(res, "Failed to store access token");
}
}

View File

@@ -0,0 +1,42 @@
import { findGiteaById } from "@dokploy/server";
import type { NextApiResponse } from "next";
// Shared Gitea interface
export interface Gitea {
giteaId: string;
gitProviderId: string;
redirectUri: string | null;
accessToken: string | null;
refreshToken: string | null;
expiresAt: number | null;
giteaUrl: string;
clientId: string | null;
clientSecret: string | null;
organizationName?: string;
gitProvider: {
name: string;
gitProviderId: string;
providerType: "github" | "gitlab" | "bitbucket" | "gitea";
createdAt: string;
organizationId: string;
};
}
// Shared function to find Gitea by ID
export const findGitea = async (giteaId: string): Promise<Gitea | null> => {
try {
const gitea = await findGiteaById(giteaId);
return gitea;
} catch (findError) {
console.error("Error finding Gitea provider:", findError);
return null;
}
};
// Helper for redirecting with error message
export const redirectWithError = (res: NextApiResponse, error: string) => {
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent(error)}`,
);
};

View File

@@ -34,6 +34,15 @@ import {
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
DropdownMenu,
DropdownMenuContent,
@@ -47,6 +56,13 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
@@ -64,8 +80,8 @@ import {
Loader2,
PlusIcon,
Search,
X,
Trash2,
X,
} from "lucide-react";
import type {
GetServerSidePropsContext,
@@ -73,25 +89,9 @@ import type {
} from "next";
import Head from "next/head";
import { useRouter } from "next/router";
import { type ReactElement, useMemo, useState, useEffect } from "react";
import { type ReactElement, useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import superjson from "superjson";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export type Services = {
appName: string;

View File

@@ -1,3 +1,4 @@
import { ShowImport } from "@/components/dashboard/application/advanced/import/show-import";
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command";
@@ -47,7 +48,6 @@ import { useRouter } from "next/router";
import { type ReactElement, useEffect, useState } from "react";
import { toast } from "sonner";
import superjson from "superjson";
import { ShowImport } from "@/components/dashboard/application/advanced/import/show-import";
type TabState =
| "projects"

View File

@@ -8,11 +8,11 @@ import { ShowExternalPostgresCredentials } from "@/components/dashboard/postgres
import { ShowGeneralPostgres } from "@/components/dashboard/postgres/general/show-general-postgres";
import { ShowInternalPostgresCredentials } from "@/components/dashboard/postgres/general/show-internal-postgres-credentials";
import { UpdatePostgres } from "@/components/dashboard/postgres/update-postgres";
import { ShowDatabaseAdvancedSettings } from "@/components/dashboard/shared/show-database-advanced-settings";
import { PostgresqlIcon } from "@/components/icons/data-tools-icons";
import { ProjectLayout } from "@/components/layouts/project-layout";
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
import { StatusTooltip } from "@/components/shared/status-tooltip";
import { ShowDatabaseAdvancedSettings } from "@/components/dashboard/shared/show-database-advanced-settings";
import { Badge } from "@/components/ui/badge";
import {
Card,

View File

@@ -5,7 +5,7 @@ import { getLocale, serverSideTranslations } from "@/utils/i18n";
import { validateRequest } from "@dokploy/server";
import { createServerSideHelpers } from "@trpc/react-query/server";
import type { GetServerSidePropsContext } from "next";
import React, { type ReactElement } from "react";
import type { ReactElement } from "react";
import superjson from "superjson";
const Page = () => {

View File

@@ -13,9 +13,9 @@ import { destinationRouter } from "./routers/destination";
import { dockerRouter } from "./routers/docker";
import { domainRouter } from "./routers/domain";
import { gitProviderRouter } from "./routers/git-provider";
import { giteaRouter } from "./routers/gitea";
import { githubRouter } from "./routers/github";
import { gitlabRouter } from "./routers/gitlab";
import { giteaRouter } from "./routers/gitea";
import { mariadbRouter } from "./routers/mariadb";
import { mongoRouter } from "./routers/mongo";
import { mountRouter } from "./routers/mount";

View File

@@ -26,8 +26,8 @@ import {
checkServiceAccess,
} from "@dokploy/server/services/user";
import {
getProviderHeaders,
type Model,
getProviderHeaders,
} from "@dokploy/server/utils/ai/select-ai-provider";
import { TRPCError } from "@trpc/server";
import { z } from "zod";

View File

@@ -14,9 +14,9 @@ import {
apiSaveDockerProvider,
apiSaveEnvironmentVariables,
apiSaveGitProvider,
apiSaveGiteaProvider,
apiSaveGithubProvider,
apiSaveGitlabProvider,
apiSaveGiteaProvider,
apiUpdateApplication,
applications,
} from "@/server/db/schema";
@@ -425,7 +425,7 @@ export const applicationRouter = createTRPCRouter({
giteaProjectId: input.giteaProjectId,
giteaPathNamespace: input.giteaPathNamespace,
});
return true;
}),
saveDockerProvider: protectedProcedure

View File

@@ -9,23 +9,10 @@ import {
apiUpdateCompose,
compose as composeTable,
} from "@/server/db/schema";
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
import { generatePassword } from "@/templates/utils";
import {
type CompleteTemplate,
fetchTemplateFiles,
fetchTemplatesList,
} from "@dokploy/server/templates/github";
import { processTemplate } from "@dokploy/server/templates/processors";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { dump, load } from "js-yaml";
import _ from "lodash";
import { nanoid } from "nanoid";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
import { z } from "zod";
import type { DeploymentJob } from "@/server/queues/queue-types";
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
import { deploy } from "@/server/utils/deploy";
import { generatePassword } from "@/templates/utils";
import {
IS_CLOUD,
addDomainToCompose,
@@ -55,6 +42,19 @@ import {
stopCompose,
updateCompose,
} from "@dokploy/server";
import {
type CompleteTemplate,
fetchTemplateFiles,
fetchTemplatesList,
} from "@dokploy/server/templates/github";
import { processTemplate } from "@dokploy/server/templates/processors";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { dump, load } from "js-yaml";
import _ from "lodash";
import { nanoid } from "nanoid";
import { z } from "zod";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
export const composeRouter = createTRPCRouter({
create: protectedProcedure

View File

@@ -21,7 +21,7 @@ import {
updateDestinationById,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { eq, desc } from "drizzle-orm";
import { desc, eq } from "drizzle-orm";
export const destinationRouter = createTRPCRouter({
create: adminProcedure

View File

@@ -1,238 +1,240 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiCreateGitea,
apiFindOneGitea,
apiGiteaTestConnection,
apiUpdateGitea,
apiFindGiteaBranches,
apiCreateGitea,
apiFindGiteaBranches,
apiFindOneGitea,
apiGiteaTestConnection,
apiUpdateGitea,
} from "@/server/db/schema";
import { db } from "@/server/db";
import {
createGitea,
findGiteaById,
haveGiteaRequirements,
testGiteaConnection,
updateGitProvider,
updateGitea,
getGiteaBranches,
getGiteaRepositories,
createGitea,
findGiteaById,
getGiteaBranches,
getGiteaRepositories,
haveGiteaRequirements,
testGiteaConnection,
updateGitProvider,
updateGitea,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
// Gitea Router
export const giteaRouter = createTRPCRouter({
// Create a new Gitea provider
create: protectedProcedure
.input(apiCreateGitea)
.mutation(async ({ input, ctx }) => {
try {
return await createGitea(input, ctx.session.activeOrganizationId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error creating this Gitea provider",
cause: error,
});
}
}),
// Create a new Gitea provider
create: protectedProcedure
.input(apiCreateGitea)
.mutation(async ({ input, ctx }) => {
try {
return await createGitea(input, ctx.session.activeOrganizationId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error creating this Gitea provider",
cause: error,
});
}
}),
// Fetch a specific Gitea provider by ID
one: protectedProcedure
.input(apiFindOneGitea)
.query(async ({ input, ctx }) => {
const giteaProvider = await findGiteaById(input.giteaId);
if (
giteaProvider.gitProvider.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider",
});
}
return giteaProvider;
}),
// Fetch a specific Gitea provider by ID
one: protectedProcedure
.input(apiFindOneGitea)
.query(async ({ input, ctx }) => {
const giteaProvider = await findGiteaById(input.giteaId);
if (
giteaProvider.gitProvider.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider",
});
}
return giteaProvider;
}),
// Fetch all Gitea providers for the active organization
giteaProviders: protectedProcedure.query(async ({ ctx }) => {
let result = await db.query.gitea.findMany({
with: {
gitProvider: true,
},
});
// Fetch all Gitea providers for the active organization
giteaProviders: protectedProcedure.query(async ({ ctx }) => {
let result = await db.query.gitea.findMany({
with: {
gitProvider: true,
},
});
// Filter by organization ID
result = result.filter(
(provider) =>
provider.gitProvider.organizationId === ctx.session.activeOrganizationId
);
// Filter providers that meet the requirements
const filtered = result
.filter((provider) => haveGiteaRequirements(provider))
.map((provider) => {
return {
giteaId: provider.giteaId,
gitProvider: {
...provider.gitProvider,
},
};
});
// Filter by organization ID
result = result.filter(
(provider) =>
provider.gitProvider.organizationId ===
ctx.session.activeOrganizationId,
);
return filtered;
}),
// Filter providers that meet the requirements
const filtered = result
.filter((provider) => haveGiteaRequirements(provider))
.map((provider) => {
return {
giteaId: provider.giteaId,
gitProvider: {
...provider.gitProvider,
},
};
});
// Fetch repositories from Gitea provider
getGiteaRepositories: protectedProcedure
.input(apiFindOneGitea)
.query(async ({ input, ctx }) => {
const { giteaId } = input;
return filtered;
}),
if (!giteaId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Gitea provider ID is required.",
});
}
// Fetch repositories from Gitea provider
getGiteaRepositories: protectedProcedure
.input(apiFindOneGitea)
.query(async ({ input, ctx }) => {
const { giteaId } = input;
const giteaProvider = await findGiteaById(giteaId);
if (
giteaProvider.gitProvider.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider",
});
}
try {
// Call the service layer function to get repositories
console.log('Calling getGiteaRepositories with giteaId:', giteaId);
return await getGiteaRepositories(giteaId);
} catch (error) {
console.error('Error fetching Gitea repositories:', error);
throw new TRPCError({
code: "BAD_REQUEST",
message: error instanceof Error ? error.message : String(error),
});
}
}),
if (!giteaId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Gitea provider ID is required.",
});
}
// Fetch branches of a specific Gitea repository
getGiteaBranches: protectedProcedure
.input(apiFindGiteaBranches)
.query(async ({ input, ctx }) => {
const { giteaId, owner, repositoryName } = input;
const giteaProvider = await findGiteaById(giteaId);
if (
giteaProvider.gitProvider.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider",
});
}
if (!giteaId || !owner || !repositoryName) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Gitea provider ID, owner, and repository name are required.",
});
}
try {
// Call the service layer function to get repositories
console.log("Calling getGiteaRepositories with giteaId:", giteaId);
return await getGiteaRepositories(giteaId);
} catch (error) {
console.error("Error fetching Gitea repositories:", error);
throw new TRPCError({
code: "BAD_REQUEST",
message: error instanceof Error ? error.message : String(error),
});
}
}),
const giteaProvider = await findGiteaById(giteaId);
if (
giteaProvider.gitProvider.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider",
});
}
try {
// Call the service layer function with the required parameters
console.log('Calling getGiteaBranches with:', {
giteaId,
owner,
repo: repositoryName
});
return await getGiteaBranches({
giteaId,
owner,
repo: repositoryName,
id: 0 // Provide a default value for the optional id
});
} catch (error) {
console.error('Error fetching Gitea branches:', error);
throw new TRPCError({
code: "BAD_REQUEST",
message: error instanceof Error ? error.message : String(error),
});
}
}),
// Fetch branches of a specific Gitea repository
getGiteaBranches: protectedProcedure
.input(apiFindGiteaBranches)
.query(async ({ input, ctx }) => {
const { giteaId, owner, repositoryName } = input;
// Test connection to Gitea provider
testConnection: protectedProcedure
.input(apiGiteaTestConnection)
.mutation(async ({ input, ctx }) => {
// Ensure giteaId is always a non-empty string
const giteaId = input.giteaId ?? '';
if (!giteaId || !owner || !repositoryName) {
throw new TRPCError({
code: "BAD_REQUEST",
message:
"Gitea provider ID, owner, and repository name are required.",
});
}
try {
const giteaProvider = await findGiteaById(giteaId);
if (
giteaProvider.gitProvider.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider",
});
}
const result = await testGiteaConnection({
giteaId,
});
const giteaProvider = await findGiteaById(giteaId);
if (
giteaProvider.gitProvider.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider",
});
}
return `Found ${result} repositories`;
} catch (error) {
console.error('Gitea connection test error:', error);
throw new TRPCError({
code: "BAD_REQUEST",
message: error instanceof Error ? error.message : String(error),
});
}
}),
try {
// Call the service layer function with the required parameters
console.log("Calling getGiteaBranches with:", {
giteaId,
owner,
repo: repositoryName,
});
// Update an existing Gitea provider
update: protectedProcedure
.input(apiUpdateGitea)
.mutation(async ({ input, ctx }) => {
const giteaProvider = await findGiteaById(input.giteaId);
if (
giteaProvider.gitProvider.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider",
});
}
console.log('Updating Gitea provider:', input);
if (input.name) {
await updateGitProvider(input.gitProviderId, {
name: input.name,
organizationId: ctx.session.activeOrganizationId,
});
return await getGiteaBranches({
giteaId,
owner,
repo: repositoryName,
id: 0, // Provide a default value for the optional id
});
} catch (error) {
console.error("Error fetching Gitea branches:", error);
throw new TRPCError({
code: "BAD_REQUEST",
message: error instanceof Error ? error.message : String(error),
});
}
}),
await updateGitea(input.giteaId, {
...input,
});
} else {
await updateGitea(input.giteaId, {
...input,
});
}
return { success: true };
}),
});
// Test connection to Gitea provider
testConnection: protectedProcedure
.input(apiGiteaTestConnection)
.mutation(async ({ input, ctx }) => {
// Ensure giteaId is always a non-empty string
const giteaId = input.giteaId ?? "";
try {
const giteaProvider = await findGiteaById(giteaId);
if (
giteaProvider.gitProvider.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider",
});
}
const result = await testGiteaConnection({
giteaId,
});
return `Found ${result} repositories`;
} catch (error) {
console.error("Gitea connection test error:", error);
throw new TRPCError({
code: "BAD_REQUEST",
message: error instanceof Error ? error.message : String(error),
});
}
}),
// Update an existing Gitea provider
update: protectedProcedure
.input(apiUpdateGitea)
.mutation(async ({ input, ctx }) => {
const giteaProvider = await findGiteaById(input.giteaId);
if (
giteaProvider.gitProvider.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider",
});
}
console.log("Updating Gitea provider:", input);
if (input.name) {
await updateGitProvider(input.gitProviderId, {
name: input.name,
organizationId: ctx.session.activeOrganizationId,
});
await updateGitea(input.giteaId, {
...input,
});
} else {
await updateGitea(input.giteaId, {
...input,
});
}
return { success: true };
}),
});

View File

@@ -1,14 +1,15 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { db } from "@/server/db";
import {
apiChangeMariaDBStatus,
apiCreateMariaDB,
apiDeployMariaDB,
apiFindOneMariaDB,
apiRebuildMariadb,
apiResetMariadb,
apiSaveEnvironmentVariablesMariaDB,
apiSaveExternalPortMariaDB,
apiUpdateMariaDB,
apiRebuildMariadb,
mariadb as mariadbTable,
} from "@/server/db/schema";
import { cancelJobs } from "@/server/utils/backup";
@@ -30,12 +31,11 @@ import {
stopServiceRemote,
updateMariadbById,
} from "@dokploy/server";
import { rebuildDatabase } from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { z } from "zod";
import { eq } from "drizzle-orm";
import { db } from "@/server/db";
import { rebuildDatabase } from "@dokploy/server";
import { z } from "zod";
export const mariadbRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateMariaDB)

View File

@@ -1,4 +1,5 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { db } from "@/server/db";
import {
apiChangeMongoStatus,
apiCreateMongo,
@@ -30,12 +31,11 @@ import {
stopServiceRemote,
updateMongoById,
} from "@dokploy/server";
import { rebuildDatabase } from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { z } from "zod";
import { eq } from "drizzle-orm";
import { db } from "@/server/db";
import { rebuildDatabase } from "@dokploy/server";
import { z } from "zod";
export const mongoRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateMongo)

View File

@@ -14,6 +14,7 @@ import {
import { TRPCError } from "@trpc/server";
import { db } from "@/server/db";
import { cancelJobs } from "@/server/utils/backup";
import {
IS_CLOUD,
@@ -36,7 +37,6 @@ import {
} from "@dokploy/server";
import { observable } from "@trpc/server/observable";
import { eq } from "drizzle-orm";
import { db } from "@/server/db";
import { z } from "zod";
export const mysqlRouter = createTRPCRouter({

View File

@@ -1,4 +1,5 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { db } from "@/server/db";
import {
apiChangePostgresStatus,
apiCreatePostgres,
@@ -33,9 +34,8 @@ import {
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { z } from "zod";
import { eq } from "drizzle-orm";
import { db } from "@/server/db";
import { z } from "zod";
export const postgresRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreatePostgres)

View File

@@ -4,16 +4,17 @@ import {
apiCreateRedis,
apiDeployRedis,
apiFindOneRedis,
apiRebuildRedis,
apiResetRedis,
apiSaveEnvironmentVariablesRedis,
apiSaveExternalPortRedis,
apiUpdateRedis,
redis as redisTable,
apiRebuildRedis,
} from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import { db } from "@/server/db";
import {
IS_CLOUD,
addNewService,
@@ -31,11 +32,10 @@ import {
stopServiceRemote,
updateRedisById,
} from "@dokploy/server";
import { rebuildDatabase } from "@dokploy/server";
import { observable } from "@trpc/server/observable";
import { eq } from "drizzle-orm";
import { db } from "@/server/db";
import { z } from "zod";
import { rebuildDatabase } from "@dokploy/server";
export const redisRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateRedis)

View File

@@ -26,6 +26,7 @@ import {
findUserById,
getDokployImage,
getDokployImageTag,
getLogCleanupStatus,
getUpdateData,
initializeTraefik,
parseRawConfig,
@@ -41,6 +42,8 @@ import {
recreateDirectory,
sendDockerCleanupNotifications,
spawnAsync,
startLogCleanup,
stopLogCleanup,
updateLetsEncryptEmail,
updateServerById,
updateServerTraefik,
@@ -48,9 +51,6 @@ import {
writeConfig,
writeMainConfig,
writeTraefikConfigInPath,
startLogCleanup,
stopLogCleanup,
getLogCleanupStatus,
} from "@dokploy/server";
import { checkGPUStatus, setupGPUSupport } from "@dokploy/server";
import { generateOpenApiDocument } from "@dokploy/trpc-openapi";

View File

@@ -1,11 +1,11 @@
import {
IS_CLOUD,
createApiKey,
findOrganizationById,
findUserById,
getUserByToken,
removeUserById,
updateUser,
createApiKey,
} from "@dokploy/server";
import { db } from "@dokploy/server/db";
import {
@@ -13,12 +13,12 @@ import {
apiAssignPermissions,
apiFindOneToken,
apiUpdateUser,
apikey,
invitation,
member,
apikey,
} from "@dokploy/server/db/schema";
import * as bcrypt from "bcrypt";
import { TRPCError } from "@trpc/server";
import * as bcrypt from "bcrypt";
import { and, asc, eq, gt } from "drizzle-orm";
import { z } from "zod";
import {

View File

@@ -77,7 +77,7 @@ export function generate(schema: Schema): Template {
"CLIENT_SECRET_GITLAB_LOGIN=",
"",
"CLIENT_ID_GITEA_LOGIN=",
"CLIENT_SECRET_GITEA_LOGIN=",
"CLIENT_SECRET_GITEA_LOGIN=",
"",
"CAPTCHA_SECRET=",
"",

View File

@@ -13,9 +13,9 @@ import { z } from "zod";
import { bitbucket } from "./bitbucket";
import { deployments } from "./deployment";
import { domains } from "./domain";
import { gitea } from "./gitea";
import { github } from "./github";
import { gitlab } from "./gitlab";
import { gitea } from "./gitea";
import { mounts } from "./mount";
import { ports } from "./port";
import { previewDeployments } from "./preview-deployments";
@@ -395,7 +395,9 @@ const createSchema = createInsertSchema(applications, {
customGitUrl: z.string().optional(),
buildPath: z.string().optional(),
projectId: z.string(),
sourceType: z.enum(["github", "docker", "git", "gitlab", "bitbucket", "gitea", "drop"]).optional(),
sourceType: z
.enum(["github", "docker", "git", "gitlab", "bitbucket", "gitea", "drop"])
.optional(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
buildType: z.enum([
"dockerfile",
@@ -556,4 +558,4 @@ export const apiUpdateApplication = createSchema
.extend({
applicationId: z.string().min(1),
})
.omit({ serverId: true });
.omit({ serverId: true });

View File

@@ -6,6 +6,7 @@ import { z } from "zod";
import { bitbucket } from "./bitbucket";
import { deployments } from "./deployment";
import { domains } from "./domain";
import { gitea } from "./gitea";
import { github } from "./github";
import { gitlab } from "./gitlab";
import { mounts } from "./mount";
@@ -14,7 +15,6 @@ import { server } from "./server";
import { applicationStatus } from "./shared";
import { sshKeys } from "./ssh-key";
import { generateAppName } from "./utils";
import { gitea } from "./gitea";
export const sourceTypeCompose = pgEnum("sourceTypeCompose", [
"git",
@@ -58,9 +58,9 @@ export const compose = pgTable("compose", {
bitbucketOwner: text("bitbucketOwner"),
bitbucketBranch: text("bitbucketBranch"),
// Gitea
giteaRepository: text("giteaRepository"),
giteaOwner: text("giteaOwner"),
giteaBranch: text("giteaBranch"),
giteaRepository: text("giteaRepository"),
giteaOwner: text("giteaOwner"),
giteaBranch: text("giteaBranch"),
// Git
customGitUrl: text("customGitUrl"),
customGitBranch: text("customGitBranch"),
@@ -94,8 +94,8 @@ export const compose = pgTable("compose", {
onDelete: "set null",
}),
giteaId: text("giteaId").references(() => gitea.giteaId, {
onDelete: "set null",
}),
onDelete: "set null",
}),
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
@@ -126,9 +126,9 @@ export const composeRelations = relations(compose, ({ one, many }) => ({
references: [bitbucket.bitbucketId],
}),
gitea: one(gitea, {
fields: [compose.giteaId],
references: [gitea.giteaId],
}),
fields: [compose.giteaId],
references: [gitea.giteaId],
}),
server: one(server, {
fields: [compose.serverId],
references: [server.serverId],

View File

@@ -5,9 +5,9 @@ import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
import { bitbucket } from "./bitbucket";
import { gitea } from "./gitea";
import { github } from "./github";
import { gitlab } from "./gitlab";
import { gitea } from "./gitea";
export const gitProviderType = pgEnum("gitProviderType", [
"github",

View File

@@ -7,31 +7,31 @@ import { gitProvider } from "./git-provider";
// Gitea table definition
export const gitea = pgTable("gitea", {
giteaId: text("giteaId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()), // Using nanoid for unique ID
giteaUrl: text("giteaUrl").default("https://gitea.com").notNull(), // Default URL for Gitea
redirectUri: text("redirect_uri"),
clientId: text("client_id"),
clientSecret: text("client_secret"),
gitProviderId: text("gitProviderId")
.notNull()
.references(() => gitProvider.gitProviderId, { onDelete: "cascade" }),
giteaUsername: text("gitea_username"),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
expiresAt: integer("expires_at"),
scopes: text("scopes").default('repo,repo:status,read:user,read:org'),
lastAuthenticatedAt: integer("last_authenticated_at"),
giteaId: text("giteaId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()), // Using nanoid for unique ID
giteaUrl: text("giteaUrl").default("https://gitea.com").notNull(), // Default URL for Gitea
redirectUri: text("redirect_uri"),
clientId: text("client_id"),
clientSecret: text("client_secret"),
gitProviderId: text("gitProviderId")
.notNull()
.references(() => gitProvider.gitProviderId, { onDelete: "cascade" }),
giteaUsername: text("gitea_username"),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
expiresAt: integer("expires_at"),
scopes: text("scopes").default("repo,repo:status,read:user,read:org"),
lastAuthenticatedAt: integer("last_authenticated_at"),
});
// Gitea relations with gitProvider
export const giteaProviderRelations = relations(gitea, ({ one }) => ({
gitProvider: one(gitProvider, {
fields: [gitea.gitProviderId],
references: [gitProvider.gitProviderId],
}),
gitProvider: one(gitProvider, {
fields: [gitea.gitProviderId],
references: [gitProvider.gitProviderId],
}),
}));
// Create schema for Gitea
@@ -39,58 +39,58 @@ const createSchema = createInsertSchema(gitea);
// API schema for creating a Gitea instance
export const apiCreateGitea = createSchema.extend({
clientId: z.string().optional(),
clientSecret: z.string().optional(),
gitProviderId: z.string().optional(),
redirectUri: z.string().optional(),
name: z.string().min(1),
giteaUrl: z.string().min(1),
giteaUsername: z.string().optional(),
accessToken: z.string().optional(),
refreshToken: z.string().optional(),
expiresAt: z.number().optional(),
organizationName: z.string().optional(),
scopes: z.string().optional(),
lastAuthenticatedAt: z.number().optional(),
clientId: z.string().optional(),
clientSecret: z.string().optional(),
gitProviderId: z.string().optional(),
redirectUri: z.string().optional(),
name: z.string().min(1),
giteaUrl: z.string().min(1),
giteaUsername: z.string().optional(),
accessToken: z.string().optional(),
refreshToken: z.string().optional(),
expiresAt: z.number().optional(),
organizationName: z.string().optional(),
scopes: z.string().optional(),
lastAuthenticatedAt: z.number().optional(),
});
// API schema for finding one Gitea instance
export const apiFindOneGitea = createSchema
.extend({
giteaId: z.string().min(1),
})
.pick({ giteaId: true });
.extend({
giteaId: z.string().min(1),
})
.pick({ giteaId: true });
// API schema for testing Gitea connection
export const apiGiteaTestConnection = createSchema
.extend({
organizationName: z.string().optional(),
})
.pick({ giteaId: true, organizationName: true });
.extend({
organizationName: z.string().optional(),
})
.pick({ giteaId: true, organizationName: true });
export type ApiGiteaTestConnection = z.infer<typeof apiGiteaTestConnection>;
// API schema for finding branches in Gitea
export const apiFindGiteaBranches = z.object({
id: z.number().optional(),
owner: z.string().min(1),
repositoryName: z.string().min(1),
giteaId: z.string().optional(),
id: z.number().optional(),
owner: z.string().min(1),
repositoryName: z.string().min(1),
giteaId: z.string().optional(),
});
// API schema for updating Gitea instance
export const apiUpdateGitea = createSchema.extend({
clientId: z.string().optional(),
clientSecret: z.string().optional(),
redirectUri: z.string().optional(),
name: z.string().min(1),
giteaId: z.string().min(1),
giteaUrl: z.string().min(1),
giteaUsername: z.string().optional(),
accessToken: z.string().optional(),
refreshToken: z.string().optional(),
expiresAt: z.number().optional(),
organizationName: z.string().optional(),
scopes: z.string().optional(),
lastAuthenticatedAt: z.number().optional(),
});
clientId: z.string().optional(),
clientSecret: z.string().optional(),
redirectUri: z.string().optional(),
name: z.string().min(1),
giteaId: z.string().min(1),
giteaUrl: z.string().min(1),
giteaUsername: z.string().optional(),
accessToken: z.string().optional(),
refreshToken: z.string().optional(),
expiresAt: z.number().optional(),
organizationName: z.string().optional(),
scopes: z.string().optional(),
lastAuthenticatedAt: z.number().optional(),
});

View File

@@ -10,7 +10,7 @@ import {
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { account, organization, apikey } from "./account";
import { account, apikey, organization } from "./account";
import { projects } from "./project";
import { certificateType } from "./shared";
/**

View File

@@ -2,12 +2,12 @@ import type { IncomingMessage } from "node:http";
import * as bcrypt from "bcrypt";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { organization, twoFactor, apiKey } from "better-auth/plugins";
import { apiKey, organization, twoFactor } from "better-auth/plugins";
import { and, desc, eq } from "drizzle-orm";
import { IS_CLOUD } from "../constants";
import { db } from "../db";
import * as schema from "../db/schema";
import { sendEmail } from "../verification/send-verification-email";
import { IS_CLOUD } from "../constants";
const { handler, api } = betterAuth({
database: drizzleAdapter(db, {

View File

@@ -6,8 +6,8 @@ import { generateObject } from "ai";
import { desc, eq } from "drizzle-orm";
import { z } from "zod";
import { IS_CLOUD } from "../constants";
import { findServerById } from "./server";
import { findOrganizationById } from "./admin";
import { findServerById } from "./server";
export const getAiSettingsByOrganizationId = async (organizationId: string) => {
const aiSettings = await db.query.ai.findMany({

View File

@@ -26,6 +26,10 @@ import {
cloneGitRepository,
getCustomGitCloneCommand,
} from "@dokploy/server/utils/providers/git";
import {
cloneGiteaRepository,
getGiteaCloneCommand,
} from "@dokploy/server/utils/providers/gitea";
import {
cloneGithubRepository,
getGithubCloneCommand,
@@ -34,10 +38,6 @@ import {
cloneGitlabRepository,
getGitlabCloneCommand,
} from "@dokploy/server/utils/providers/gitlab";
import {
cloneGiteaRepository,
getGiteaCloneCommand,
} from "@dokploy/server/utils/providers/gitea";
import { createTraefikConfig } from "@dokploy/server/utils/traefik/application";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
@@ -318,10 +318,7 @@ export const deployRemoteApplication = async ({
deployment.logPath,
);
} else if (application.sourceType === "gitea") {
command += await getGiteaCloneCommand(
application,
deployment.logPath,
);
command += await getGiteaCloneCommand(application, deployment.logPath);
} else if (application.sourceType === "git") {
command += await getCustomGitCloneCommand(
application,

View File

@@ -29,6 +29,10 @@ import {
cloneGitRepository,
getCustomGitCloneCommand,
} from "@dokploy/server/utils/providers/git";
import {
cloneGiteaRepository,
getGiteaCloneCommand,
} from "@dokploy/server/utils/providers/gitea";
import {
cloneGithubRepository,
getGithubCloneCommand,
@@ -37,10 +41,6 @@ import {
cloneGitlabRepository,
getGitlabCloneCommand,
} from "@dokploy/server/utils/providers/gitlab";
import {
cloneGiteaRepository,
getGiteaCloneCommand,
} from "@dokploy/server/utils/providers/gitea";
import {
createComposeFile,
getCreateComposeFileCommand,
@@ -237,7 +237,7 @@ export const deployCompose = async ({
await cloneGiteaRepository(compose, deployment.logPath, true);
} else if (compose.sourceType === "raw") {
await createComposeFile(compose, deployment.logPath);
}
}
await buildCompose(compose, deployment.logPath);
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateCompose(composeId, {
@@ -360,9 +360,9 @@ export const deployRemoteCompose = async ({
command += getCreateComposeFileCommand(compose, deployment.logPath);
} else if (compose.sourceType === "gitea") {
command += await getGiteaCloneCommand(
compose,
deployment.logPath,
true
compose,
deployment.logPath,
true,
);
}

View File

@@ -1,104 +1,100 @@
import { db } from "@dokploy/server/db";
import {
type apiCreateGitea,
gitProvider,
gitea,
type apiCreateGitea,
gitProvider,
gitea,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
export type Gitea = typeof gitea.$inferSelect;
export const createGitea = async (
input: typeof apiCreateGitea._type,
organizationId: string,
input: typeof apiCreateGitea._type,
organizationId: string,
) => {
return await db.transaction(async (tx) => {
// Insert new Git provider (Gitea)
const newGitProvider = await tx
.insert(gitProvider)
.values({
providerType: "gitea", // Set providerType to 'gitea'
organizationId: organizationId,
name: input.name,
})
.returning()
.then((response) => response[0]);
return await db.transaction(async (tx) => {
// Insert new Git provider (Gitea)
const newGitProvider = await tx
.insert(gitProvider)
.values({
providerType: "gitea", // Set providerType to 'gitea'
organizationId: organizationId,
name: input.name,
})
.returning()
.then((response) => response[0]);
if (!newGitProvider) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error creating the Git provider",
});
}
if (!newGitProvider) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error creating the Git provider",
});
}
// Insert the Gitea data into the `gitea` table
await tx
.insert(gitea)
.values({
...input,
gitProviderId: newGitProvider?.gitProviderId,
})
.returning()
.then((response) => response[0]);
});
// Insert the Gitea data into the `gitea` table
await tx
.insert(gitea)
.values({
...input,
gitProviderId: newGitProvider?.gitProviderId,
})
.returning()
.then((response) => response[0]);
});
};
export const findGiteaById = async (giteaId: string) => {
try {
const giteaProviderResult = await db.query.gitea.findFirst({
where: eq(gitea.giteaId, giteaId),
with: {
gitProvider: true,
},
});
if (!giteaProviderResult) {
console.error('No Gitea Provider found:', { giteaId });
throw new TRPCError({
code: "NOT_FOUND",
message: "Gitea Provider not found",
});
}
return giteaProviderResult;
} catch (error) {
console.error('Error finding Gitea Provider:', error);
throw error;
}
try {
const giteaProviderResult = await db.query.gitea.findFirst({
where: eq(gitea.giteaId, giteaId),
with: {
gitProvider: true,
},
});
if (!giteaProviderResult) {
console.error("No Gitea Provider found:", { giteaId });
throw new TRPCError({
code: "NOT_FOUND",
message: "Gitea Provider not found",
});
}
return giteaProviderResult;
} catch (error) {
console.error("Error finding Gitea Provider:", error);
throw error;
}
};
export const updateGitea = async (
giteaId: string,
input: Partial<Gitea>,
) => {
console.log('Updating Gitea Provider:', {
giteaId,
updateData: {
accessTokenPresent: !!input.accessToken,
refreshTokenPresent: !!input.refreshToken,
expiresAt: input.expiresAt,
}
});
export const updateGitea = async (giteaId: string, input: Partial<Gitea>) => {
console.log("Updating Gitea Provider:", {
giteaId,
updateData: {
accessTokenPresent: !!input.accessToken,
refreshTokenPresent: !!input.refreshToken,
expiresAt: input.expiresAt,
},
});
try {
const updateResult = await db
.update(gitea)
.set(input)
.where(eq(gitea.giteaId, giteaId))
.returning();
try {
const updateResult = await db
.update(gitea)
.set(input)
.where(eq(gitea.giteaId, giteaId))
.returning();
// Explicitly type the result and handle potential undefined
const result = updateResult[0] as Gitea | undefined;
// Explicitly type the result and handle potential undefined
const result = updateResult[0] as Gitea | undefined;
if (!result) {
console.error('No rows were updated', { giteaId, input });
throw new Error(`Failed to update Gitea provider with ID ${giteaId}`);
}
if (!result) {
console.error("No rows were updated", { giteaId, input });
throw new Error(`Failed to update Gitea provider with ID ${giteaId}`);
}
return result;
} catch (error) {
console.error('Error updating Gitea provider:', error);
throw error;
}
};
return result;
} catch (error) {
console.error("Error updating Gitea provider:", error);
throw error;
}
};

View File

@@ -6,10 +6,10 @@ import {
} from "@dokploy/server/services/deployment";
import { findServerById } from "@dokploy/server/services/server";
import {
TRAEFIK_HTTP3_PORT,
TRAEFIK_PORT,
TRAEFIK_SSL_PORT,
TRAEFIK_VERSION,
TRAEFIK_HTTP3_PORT,
getDefaultMiddlewares,
getDefaultServerTraefikConfig,
} from "@dokploy/server/setup/traefik-setup";

View File

@@ -1,8 +1,8 @@
import { paths } from "@dokploy/server/constants";
import { execAsync } from "../process/execAsync";
import { findAdmin } from "@dokploy/server/services/admin";
import { updateUser } from "@dokploy/server/services/user";
import { scheduleJob, scheduledJobs } from "node-schedule";
import { execAsync } from "../process/execAsync";
const LOG_CLEANUP_JOB_NAME = "access-log-cleanup";

View File

@@ -2,19 +2,19 @@ import path from "node:path";
import { getAllServers } from "@dokploy/server/services/server";
import { scheduleJob } from "node-schedule";
import { db } from "../../db/index";
import { findAdmin } from "../../services/admin";
import {
cleanUpDockerBuilder,
cleanUpSystemPrune,
cleanUpUnusedImages,
} from "../docker/utils";
import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import { runMariadbBackup } from "./mariadb";
import { runMongoBackup } from "./mongo";
import { runMySqlBackup } from "./mysql";
import { runPostgresBackup } from "./postgres";
import { findAdmin } from "../../services/admin";
import { getS3Credentials } from "./utils";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import type { BackupSchedule } from "@dokploy/server/services/backup";
import { startLogCleanup } from "../access-log/handler";

View File

@@ -1,11 +1,11 @@
import type { BackupSchedule } from "@dokploy/server/services/backup";
import type { Destination } from "@dokploy/server/services/destination";
import { scheduleJob, scheduledJobs } from "node-schedule";
import { keepLatestNBackups } from ".";
import { runMariadbBackup } from "./mariadb";
import { runMongoBackup } from "./mongo";
import { runMySqlBackup } from "./mysql";
import { runPostgresBackup } from "./postgres";
import { keepLatestNBackups } from ".";
export const scheduleBackup = (backup: BackupSchedule) => {
const { schedule, backupId, databaseType, postgres, mysql, mongo, mariadb } =

View File

@@ -112,118 +112,120 @@ export const getBuildCommand = (
export const mechanizeDockerContainer = async (
application: ApplicationNested,
) => {
console.log(`Starting to mechanize Docker container for ${application.appName}`);
) => {
console.log(
`Starting to mechanize Docker container for ${application.appName}`,
);
const {
appName,
env,
mounts,
cpuLimit,
memoryLimit,
memoryReservation,
cpuReservation,
command,
ports,
appName,
env,
mounts,
cpuLimit,
memoryLimit,
memoryReservation,
cpuReservation,
command,
ports,
} = application;
const resources = calculateResources({
memoryLimit,
memoryReservation,
cpuLimit,
cpuReservation,
memoryLimit,
memoryReservation,
cpuLimit,
cpuReservation,
});
const volumesMount = generateVolumeMounts(mounts);
const {
HealthCheck,
RestartPolicy,
Placement,
Labels,
Mode,
RollbackConfig,
UpdateConfig,
Networks,
HealthCheck,
RestartPolicy,
Placement,
Labels,
Mode,
RollbackConfig,
UpdateConfig,
Networks,
} = generateConfigContainer(application);
const bindsMount = generateBindMounts(mounts);
const filesMount = generateFileMounts(appName, application);
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
env,
application.project.env,
);
const image = getImageName(application);
const authConfig = getAuthConfig(application);
const docker = await getRemoteDocker(application.serverId);
const settings: CreateServiceOptions = {
authconfig: authConfig,
Name: appName,
TaskTemplate: {
ContainerSpec: {
HealthCheck,
Image: image,
Env: envVariables,
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
...(command
? {
Command: ["/bin/sh"],
Args: ["-c", command],
}
: {}),
Labels,
},
Networks,
RestartPolicy,
Placement,
Resources: {
...resources,
},
},
Mode,
RollbackConfig,
EndpointSpec: {
Ports: ports.map((port) => ({
Protocol: port.protocol,
TargetPort: port.targetPort,
PublishedPort: port.publishedPort,
})),
},
UpdateConfig,
};
try {
console.log(`Attempting to find existing service: ${appName}`);
const service = docker.getService(appName);
const inspect = await service.inspect();
console.log(`Found existing service, updating: ${appName}`);
await service.update({
version: Number.parseInt(inspect.Version.Index),
...settings,
authconfig: authConfig,
Name: appName,
TaskTemplate: {
...settings.TaskTemplate,
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
ContainerSpec: {
HealthCheck,
Image: image,
Env: envVariables,
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
...(command
? {
Command: ["/bin/sh"],
Args: ["-c", command],
}
: {}),
Labels,
},
Networks,
RestartPolicy,
Placement,
Resources: {
...resources,
},
},
});
console.log(`Service updated successfully: ${appName}`);
Mode,
RollbackConfig,
EndpointSpec: {
Ports: ports.map((port) => ({
Protocol: port.protocol,
TargetPort: port.targetPort,
PublishedPort: port.publishedPort,
})),
},
UpdateConfig,
};
try {
console.log(`Attempting to find existing service: ${appName}`);
const service = docker.getService(appName);
const inspect = await service.inspect();
console.log(`Found existing service, updating: ${appName}`);
await service.update({
version: Number.parseInt(inspect.Version.Index),
...settings,
TaskTemplate: {
...settings.TaskTemplate,
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
});
console.log(`Service updated successfully: ${appName}`);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.log(`Service not found or error: ${errorMessage}`);
console.log(`Creating new service: ${appName}`);
try {
await docker.createService(settings);
console.log(`Service created successfully: ${appName}`);
} catch (createError: unknown) {
const createErrorMessage = createError instanceof Error
? createError.message
: 'Unknown error';
console.error(`Failed to create service: ${createErrorMessage}`);
throw createError;
}
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
console.log(`Service not found or error: ${errorMessage}`);
console.log(`Creating new service: ${appName}`);
try {
await docker.createService(settings);
console.log(`Service created successfully: ${appName}`);
} catch (createError: unknown) {
const createErrorMessage =
createError instanceof Error ? createError.message : "Unknown error";
console.error(`Failed to create service: ${createErrorMessage}`);
throw createError;
}
}
};

View File

@@ -39,7 +39,7 @@ export const buildNixpacks = async (
for (const env of envVariables) {
args.push("--env", env);
}
if (publishDirectory) {
/* No need for any start command, since we'll use nginx later on */
args.push("--no-error-without-start");
@@ -90,10 +90,11 @@ export const buildNixpacks = async (
await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
} catch (rmError) {
// Ignore errors from container removal
const errorMessage = rmError instanceof Error
? rmError.message
: 'Unknown container cleanup error';
const errorMessage =
rmError instanceof Error
? rmError.message
: "Unknown container cleanup error";
// Just log it but don't let it cause another error
writeToStream(`Container cleanup attempt: ${errorMessage}\n`);
}

View File

@@ -1,11 +1,11 @@
import { createHash } from "node:crypto";
import type { WriteStream } from "node:fs";
import { nanoid } from "nanoid";
import type { ApplicationNested } from ".";
import { prepareEnvironmentVariables } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
import { spawnAsync } from "../process/spawnAsync";
import { execAsync } from "../process/execAsync";
import { nanoid } from "nanoid";
import { createHash } from "node:crypto";
import { spawnAsync } from "../process/spawnAsync";
const calculateSecretsHash = (envVariables: string[]): string => {
const hash = createHash("sha256");

View File

@@ -1,20 +1,20 @@
import { deployPostgres } from "@dokploy/server/services/postgres";
import { execAsyncRemote } from "../process/execAsync";
import { execAsync } from "../process/execAsync";
import { deployMySql } from "@dokploy/server/services/mysql";
import { deployMariadb } from "@dokploy/server/services/mariadb";
import { deployMongo } from "@dokploy/server/services/mongo";
import { deployRedis } from "@dokploy/server/services/redis";
import { removeService } from "../docker/utils";
import { db } from "@dokploy/server/db";
import {
postgres,
mysql,
mariadb,
mongo,
mysql,
postgres,
redis,
} from "@dokploy/server/db/schema";
import { deployMariadb } from "@dokploy/server/services/mariadb";
import { deployMongo } from "@dokploy/server/services/mongo";
import { deployMySql } from "@dokploy/server/services/mysql";
import { deployPostgres } from "@dokploy/server/services/postgres";
import { deployRedis } from "@dokploy/server/services/redis";
import { eq } from "drizzle-orm";
import { removeService } from "../docker/utils";
import { execAsyncRemote } from "../process/execAsync";
import { execAsync } from "../process/execAsync";
type DatabaseType = "postgres" | "mysql" | "mariadb" | "mongo" | "redis";

View File

@@ -14,6 +14,7 @@ import {
cloneGitRawRepository,
cloneRawGitRepositoryRemote,
} from "../providers/git";
import { cloneRawGiteaRepository } from "../providers/gitea";
import {
cloneRawGithubRepository,
cloneRawGithubRepositoryRemote,
@@ -22,10 +23,6 @@ import {
cloneRawGitlabRepository,
cloneRawGitlabRepositoryRemote,
} from "../providers/gitlab";
import {
cloneRawGiteaRepository,
cloneRawGiteaRepositoryRemote,
} from "../providers/gitea";
import {
createComposeFileRaw,
createComposeFileRawRemote,

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More