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 type { CompleteTemplate } from "@dokploy/server/templates/processors";
import { processTemplate } 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", () => { describe("processTemplate", () => {
// Mock schema for testing // 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 { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@@ -32,7 +33,6 @@ import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
const ImportSchema = z.object({ const ImportSchema = z.object({
base64: z.string(), base64: z.string(),

View File

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

View File

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

View File

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

View File

@@ -235,7 +235,8 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
? "Loading...." ? "Loading...."
: field.value.owner : field.value.owner
? repositories?.find( ? repositories?.find(
(repo: GiteaRepository) => repo.name === field.value.repo, (repo: GiteaRepository) =>
repo.name === field.value.repo,
)?.name )?.name
: "Select repository"} : "Select repository"}
@@ -327,7 +328,8 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
? "Loading...." ? "Loading...."
: field.value : field.value
? branches?.find( ? branches?.find(
(branch: GiteaBranch) => branch.name === field.value, (branch: GiteaBranch) =>
branch.name === field.value,
)?.name )?.name
: "Select branch"} : "Select branch"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
@@ -411,4 +413,4 @@ export const SaveGiteaProvider = ({ applicationId }: Props) => {
</Form> </Form>
</div> </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 { Button } from "@/components/ui/button";
import { import {
Command, Command,
@@ -34,17 +36,15 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import Link from "next/link";
import { GithubIcon } from "@/components/icons/data-tools-icons";
const GithubProviderSchema = z.object({ const GithubProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"), 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 { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Command, Command,
@@ -35,17 +37,15 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import Link from "next/link";
import { GitlabIcon } from "@/components/icons/data-tools-icons";
const GitlabProviderSchema = z.object({ const GitlabProviderSchema = z.object({
buildPath: z.string().min(1, "Path is required").default("/"), 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 { SaveGiteaProvider } from "@/components/dashboard/application/general/generic/save-gitea-provider";
import { SaveGithubProvider } from "@/components/dashboard/application/general/generic/save-github-provider"; import { SaveGithubProvider } from "@/components/dashboard/application/general/generic/save-github-provider";
import { import {
BitbucketIcon, BitbucketIcon,
DockerIcon, DockerIcon,
GitIcon, GitIcon,
GiteaIcon, GiteaIcon,
GithubIcon, GithubIcon,
GitlabIcon, GitlabIcon,
} from "@/components/icons/data-tools-icons"; } from "@/components/icons/data-tools-icons";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 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 { SaveDragNDrop } from "./save-drag-n-drop";
import { SaveGitlabProvider } from "./save-gitlab-provider"; 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 { interface Props {
applicationId: string; applicationId: string;
} }
export const ShowProviderForm = ({ applicationId }: Props) => { export const ShowProviderForm = ({ applicationId }: Props) => {
const { data: githubProviders } = api.github.githubProviders.useQuery(); const { data: githubProviders } = api.github.githubProviders.useQuery();
const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery(); const { data: gitlabProviders } = api.gitlab.gitlabProviders.useQuery();
const { data: bitbucketProviders } = const { data: bitbucketProviders } =
api.bitbucket.bitbucketProviders.useQuery(); api.bitbucket.bitbucketProviders.useQuery();
const { data: giteaProviders } = api.gitea.giteaProviders.useQuery(); const { data: giteaProviders } = api.gitea.giteaProviders.useQuery();
const { data: application } = api.application.one.useQuery({ applicationId }); const { data: application } = api.application.one.useQuery({ applicationId });
const [tab, setSab] = useState<TabState>(application?.sourceType || "github"); const [tab, setSab] = useState<TabState>(application?.sourceType || "github");
return ( return (
<Card className="group relative w-full bg-transparent"> <Card className="group relative w-full bg-transparent">
<CardHeader> <CardHeader>
<CardTitle className="flex items-start justify-between"> <CardTitle className="flex items-start justify-between">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="flex flex-col space-y-0.5">Provider</span> <span className="flex flex-col space-y-0.5">Provider</span>
<p className="flex items-center text-sm font-normal text-muted-foreground"> <p className="flex items-center text-sm font-normal text-muted-foreground">
Select the source of your code Select the source of your code
</p> </p>
</div> </div>
<div className="hidden space-y-1 text-sm font-normal md:block"> <div className="hidden space-y-1 text-sm font-normal md:block">
<GitBranch className="size-6 text-muted-foreground" /> <GitBranch className="size-6 text-muted-foreground" />
</div> </div>
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Tabs <Tabs
value={tab} value={tab}
className="w-full" className="w-full"
onValueChange={(e) => { onValueChange={(e) => {
setSab(e as TabState); setSab(e as TabState);
}} }}
> >
<div className="flex flex-row items-center justify-between w-full gap-4"> <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"> <TabsList className="md:grid md:w-fit md:grid-cols-7 max-md:overflow-x-scroll justify-start bg-transparent overflow-y-hidden">
<TabsTrigger <TabsTrigger
value="github" 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" 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" /> <GithubIcon className="size-4 text-current fill-current" />
Github Github
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="gitlab" 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" 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" /> <GitlabIcon className="size-4 text-current fill-current" />
Gitlab Gitlab
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="bitbucket" 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" 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" /> <BitbucketIcon className="size-4 text-current fill-current" />
Bitbucket Bitbucket
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="gitea" 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" 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" /> <GiteaIcon className="size-4 text-current fill-current" />
Gitea Gitea
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="docker" 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" 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" /> <DockerIcon className="size-5 text-current" />
Docker Docker
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="git" 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" className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
> >
<GitIcon /> <GitIcon />
Git Git
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="drop" 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" 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" /> <UploadCloud className="size-5 text-current" />
Drop Drop
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
</div> </div>
<TabsContent value="github" className="w-full p-2"> <TabsContent value="github" className="w-full p-2">
{githubProviders && githubProviders?.length > 0 ? ( {githubProviders && githubProviders?.length > 0 ? (
<SaveGithubProvider applicationId={applicationId} /> <SaveGithubProvider applicationId={applicationId} />
) : ( ) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center"> <div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<GithubIcon className="size-8 text-muted-foreground" /> <GithubIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground"> <span className="text-base text-muted-foreground">
To deploy using GitHub, you need to configure your account To deploy using GitHub, you need to configure your account
first. Please, go to{" "} first. Please, go to{" "}
<Link <Link
href="/dashboard/settings/git-providers" href="/dashboard/settings/git-providers"
className="text-foreground" className="text-foreground"
> >
Settings Settings
</Link>{" "} </Link>{" "}
to do so. to do so.
</span> </span>
</div> </div>
)} )}
</TabsContent> </TabsContent>
<TabsContent value="gitlab" className="w-full p-2"> <TabsContent value="gitlab" className="w-full p-2">
{gitlabProviders && gitlabProviders?.length > 0 ? ( {gitlabProviders && gitlabProviders?.length > 0 ? (
<SaveGitlabProvider applicationId={applicationId} /> <SaveGitlabProvider applicationId={applicationId} />
) : ( ) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center"> <div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<GitlabIcon className="size-8 text-muted-foreground" /> <GitlabIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground"> <span className="text-base text-muted-foreground">
To deploy using GitLab, you need to configure your account To deploy using GitLab, you need to configure your account
first. Please, go to{" "} first. Please, go to{" "}
<Link <Link
href="/dashboard/settings/git-providers" href="/dashboard/settings/git-providers"
className="text-foreground" className="text-foreground"
> >
Settings Settings
</Link>{" "} </Link>{" "}
to do so. to do so.
</span> </span>
</div> </div>
)} )}
</TabsContent> </TabsContent>
<TabsContent value="bitbucket" className="w-full p-2"> <TabsContent value="bitbucket" className="w-full p-2">
{bitbucketProviders && bitbucketProviders?.length > 0 ? ( {bitbucketProviders && bitbucketProviders?.length > 0 ? (
<SaveBitbucketProvider applicationId={applicationId} /> <SaveBitbucketProvider applicationId={applicationId} />
) : ( ) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center"> <div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<BitbucketIcon className="size-8 text-muted-foreground" /> <BitbucketIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground"> <span className="text-base text-muted-foreground">
To deploy using Bitbucket, you need to configure your account To deploy using Bitbucket, you need to configure your account
first. Please, go to{" "} first. Please, go to{" "}
<Link <Link
href="/dashboard/settings/git-providers" href="/dashboard/settings/git-providers"
className="text-foreground" className="text-foreground"
> >
Settings Settings
</Link>{" "} </Link>{" "}
to do so. to do so.
</span> </span>
</div> </div>
)} )}
</TabsContent> </TabsContent>
<TabsContent value="gitea" className="w-full p-2"> <TabsContent value="gitea" className="w-full p-2">
{giteaProviders && giteaProviders?.length > 0 ? ( {giteaProviders && giteaProviders?.length > 0 ? (
<SaveGiteaProvider applicationId={applicationId} /> <SaveGiteaProvider applicationId={applicationId} />
) : ( ) : (
<div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center"> <div className="flex flex-col items-center gap-3 min-h-[15vh] justify-center">
<GiteaIcon className="size-8 text-muted-foreground" /> <GiteaIcon className="size-8 text-muted-foreground" />
<span className="text-base text-muted-foreground"> <span className="text-base text-muted-foreground">
To deploy using Gitea, you need to configure your account To deploy using Gitea, you need to configure your account
first. Please, go to{" "} first. Please, go to{" "}
<Link <Link
href="/dashboard/settings/git-providers" href="/dashboard/settings/git-providers"
className="text-foreground" className="text-foreground"
> >
Settings Settings
</Link>{" "} </Link>{" "}
to do so. to do so.
</span> </span>
</div> </div>
)} )}
</TabsContent> </TabsContent>
<TabsContent value="docker" className="w-full p-2"> <TabsContent value="docker" className="w-full p-2">
<SaveDockerProvider applicationId={applicationId} /> <SaveDockerProvider applicationId={applicationId} />
</TabsContent> </TabsContent>
<TabsContent value="git" className="w-full p-2"> <TabsContent value="git" className="w-full p-2">
<SaveGitProvider applicationId={applicationId} /> <SaveGitProvider applicationId={applicationId} />
</TabsContent> </TabsContent>
<TabsContent value="drop" className="w-full p-2"> <TabsContent value="drop" className="w-full p-2">
<SaveDragNDrop applicationId={applicationId} /> <SaveDragNDrop applicationId={applicationId} />
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</CardContent> </CardContent>
</Card> </Card>
); );
}; };

View File

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

View File

@@ -7,8 +7,8 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { Ban, CheckCircle2, Hammer, HelpCircle, Terminal } from "lucide-react"; import { Ban, CheckCircle2, Hammer, HelpCircle, Terminal } from "lucide-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { toast } from "sonner"; 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 { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Command, Command,
@@ -39,13 +41,11 @@ import { cn } from "@/lib/utils";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
import Link from "next/link";
const BitbucketProviderSchema = z.object({ const BitbucketProviderSchema = z.object({
composePath: z.string().min(1), 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 { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@@ -27,13 +28,12 @@ import {
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { KeyRoundIcon, LockIcon, X } from "lucide-react"; import { KeyRoundIcon, LockIcon, X } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { GitIcon } from "@/components/icons/data-tools-icons";
import Link from "next/link";
const GitProviderSchema = z.object({ const GitProviderSchema = z.object({
composePath: z.string().min(1), 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 { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@@ -39,12 +40,11 @@ import { cn } from "@/lib/utils";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { GithubIcon } from "@/components/icons/data-tools-icons";
import Link from "next/link";
const GithubProviderSchema = z.object({ const GithubProviderSchema = z.object({
composePath: z.string().min(1), 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 { AlertBlock } from "@/components/shared/alert-block";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Command, Command,
@@ -39,13 +41,11 @@ import { cn } from "@/lib/utils";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
import Link from "next/link";
import { useEffect } from "react"; import { useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { Badge } from "@/components/ui/badge";
import { GitlabIcon } from "@/components/icons/data-tools-icons";
import Link from "next/link";
const GitlabProviderSchema = z.object({ const GitlabProviderSchema = z.object({
composePath: z.string().min(1), composePath: z.string().min(1),

View File

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

View File

@@ -286,16 +286,21 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => {
<FormItem> <FormItem>
<FormLabel>Keep the latest</FormLabel> <FormLabel>Keep the latest</FormLabel>
<FormControl> <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> </FormControl>
<FormDescription> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
); );
}} }}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="enabled" name="enabled"

View File

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

View File

@@ -92,7 +92,9 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
enabled: backup.enabled || false, enabled: backup.enabled || false,
prefix: backup.prefix, prefix: backup.prefix,
schedule: backup.schedule, schedule: backup.schedule,
keepLatestCount: backup.keepLatestCount ? Number(backup.keepLatestCount) : undefined, keepLatestCount: backup.keepLatestCount
? Number(backup.keepLatestCount)
: undefined,
}); });
} }
}, [form, form.reset, backup]); }, [form, form.reset, backup]);
@@ -274,10 +276,15 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
<FormItem> <FormItem>
<FormLabel>Keep the latest</FormLabel> <FormLabel>Keep the latest</FormLabel>
<FormControl> <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> </FormControl>
<FormDescription> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,13 +25,13 @@ import {
import { type RouterOutputs, api } from "@/utils/api"; import { type RouterOutputs, api } from "@/utils/api";
import { format } from "date-fns"; import { format } from "date-fns";
import { import {
ArrowDownUp,
AlertCircle, AlertCircle,
InfoIcon, ArrowDownUp,
Calendar as CalendarIcon, Calendar as CalendarIcon,
InfoIcon,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useState, useEffect } from "react"; import { useEffect, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { RequestDistributionChart } from "./request-distribution-chart"; import { RequestDistributionChart } from "./request-distribution-chart";
import { RequestsTable } from "./requests-table"; 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 { Button } from "@/components/ui/button";
import { api } from "@/utils/api";
import { toast } from "sonner";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
DialogDescription,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
Select, Select,
@@ -17,22 +25,14 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } 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 { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { toast } from "sonner";
import { z } from "zod"; 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({ const formSchema = z.object({
name: z.string().min(1, "Name is required"), 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 { Button } from "@/components/ui/button";
import { import {
Card, Card,
@@ -7,13 +9,11 @@ import {
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { api } from "@/utils/api"; 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 Link from "next/link";
import { toast } from "sonner"; import { toast } from "sonner";
import { formatDistanceToNow } from "date-fns";
import { DialogAction } from "@/components/shared/dialog-action";
import { AddApiKey } from "./add-api-key"; import { AddApiKey } from "./add-api-key";
import { Badge } from "@/components/ui/badge";
export const ShowApiKeys = () => { export const ShowApiKeys = () => {
const { data, refetch } = api.user.get.useQuery(); const { data, refetch } = api.user.get.useQuery();

View File

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

View File

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

View File

@@ -1,280 +1,303 @@
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { import {
Form, Form,
FormControl, FormControl,
FormField, FormField,
FormItem, FormItem,
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { useUrl } from "@/utils/hooks/use-url"; import { useUrl } from "@/utils/hooks/use-url";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon } from "lucide-react"; 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 { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { useRouter } from 'next/router';
const formSchema = z.object({ const formSchema = z.object({
name: z.string().min(1, "Name is required"), name: z.string().min(1, "Name is required"),
giteaUrl: z.string().min(1, "Gitea URL is required"), giteaUrl: z.string().min(1, "Gitea URL is required"),
clientId: z.string().min(1, "Client ID is required"), clientId: z.string().min(1, "Client ID is required"),
clientSecret: z.string().min(1, "Client Secret is required"), clientSecret: z.string().min(1, "Client Secret is required"),
}); });
interface Props { interface Props {
giteaId: string; giteaId: string;
} }
export const EditGiteaProvider = ({ giteaId }: Props) => { export const EditGiteaProvider = ({ giteaId }: Props) => {
const router = useRouter(); const router = useRouter();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const { data: gitea, isLoading, refetch } = api.gitea.one.useQuery({ giteaId }); const {
const { mutateAsync, isLoading: isUpdating } = api.gitea.update.useMutation(); data: gitea,
const { mutateAsync: testConnection, isLoading: isTesting } = api.gitea.testConnection.useMutation(); isLoading,
const url = useUrl(); refetch,
const utils = api.useUtils(); } = 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 // Handle OAuth redirect results
useEffect(() => { useEffect(() => {
const { connected, error } = router.query; const { connected, error } = router.query;
// Only process if router is ready and query parameters exist
if (!router.isReady) return;
if (connected) { // Only process if router is ready and query parameters exist
toast.success("Successfully connected to Gitea", { if (!router.isReady) return;
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 });
}
if (error) { if (connected) {
toast.error("Gitea Connection Failed", { toast.success("Successfully connected to Gitea", {
description: decodeURIComponent(error as string), description: "Your Gitea provider has been authorized.",
id: 'gitea-connection-error' id: "gitea-connection-success",
}); });
// Clear the query parameters to prevent re-triggering refetch();
router.replace({ // Clear the query parameters to prevent re-triggering
pathname: router.pathname, router.replace(
query: {} {
}, undefined, { shallow: true }); pathname: router.pathname,
} query: {},
}, [router.query, router.isReady, refetch]); },
undefined,
{ shallow: true },
);
}
const form = useForm<z.infer<typeof formSchema>>({ if (error) {
resolver: zodResolver(formSchema), toast.error("Gitea Connection Failed", {
defaultValues: { description: decodeURIComponent(error as string),
name: "", id: "gitea-connection-error",
giteaUrl: "https://gitea.com", });
clientId: "", // Clear the query parameters to prevent re-triggering
clientSecret: "", router.replace(
}, {
}); pathname: router.pathname,
query: {},
},
undefined,
{ shallow: true },
);
}
}, [router.query, router.isReady, refetch]);
// Update form values when data is loaded const form = useForm<z.infer<typeof formSchema>>({
useEffect(() => { resolver: zodResolver(formSchema),
if (gitea) { defaultValues: {
form.reset({ name: "",
name: gitea.gitProvider?.name || "", giteaUrl: "https://gitea.com",
giteaUrl: gitea.giteaUrl || "https://gitea.com", clientId: "",
clientId: gitea.clientId || "", clientSecret: "",
clientSecret: gitea.clientSecret || "", },
}); });
}
}, [gitea, form.reset]);
const onSubmit = async (values: z.infer<typeof formSchema>) => { // Update form values when data is loaded
await mutateAsync({ useEffect(() => {
giteaId: giteaId, if (gitea) {
gitProviderId: gitea?.gitProvider?.gitProviderId || "", form.reset({
name: values.name, name: gitea.gitProvider?.name || "",
giteaUrl: values.giteaUrl, giteaUrl: gitea.giteaUrl || "https://gitea.com",
clientId: values.clientId, clientId: gitea.clientId || "",
clientSecret: values.clientSecret, clientSecret: gitea.clientSecret || "",
}) });
.then(async () => { }
await utils.gitProvider.getAll.invalidate(); }, [gitea, form.reset]);
toast.success("Gitea provider updated successfully");
await refetch();
setOpen(false);
})
.catch(() => {
toast.error("Error updating Gitea provider");
});
};
const handleTestConnection = async () => { const onSubmit = async (values: z.infer<typeof formSchema>) => {
try { await mutateAsync({
const result = await testConnection({ giteaId }); giteaId: giteaId,
toast.success("Gitea Connection Verified", { gitProviderId: gitea?.gitProvider?.gitProviderId || "",
description: result name: values.name,
}); giteaUrl: values.giteaUrl,
} catch (error: any) { clientId: values.clientId,
const authUrl = error.authorizationUrl || getGiteaOAuthUrl(); clientSecret: values.clientSecret,
})
toast.error("Gitea Not Connected", { .then(async () => {
description: error.message || "Please complete the OAuth authorization process.", await utils.gitProvider.getAll.invalidate();
action: authUrl && authUrl !== "#" ? { toast.success("Gitea provider updated successfully");
label: "Authorize Now", await refetch();
onClick: () => window.open(authUrl, "_blank") setOpen(false);
} : undefined, })
}); .catch(() => {
} toast.error("Error updating Gitea provider");
}; });
};
// Generate Gitea OAuth URL with state parameter const handleTestConnection = async () => {
const getGiteaOAuthUrl = () => { try {
const clientId = form.getValues().clientId; const result = await testConnection({ giteaId });
const giteaUrl = form.getValues().giteaUrl; toast.success("Gitea Connection Verified", {
description: result,
if (!clientId || !giteaUrl) { });
toast.error("Configuration Incomplete", { } catch (error: any) {
description: "Please fill in Client ID and Gitea URL first." const authUrl = error.authorizationUrl || getGiteaOAuthUrl();
});
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 toast.error("Gitea Not Connected", {
if (isLoading) { description:
return ( error.message || "Please complete the OAuth authorization process.",
<Button variant="ghost" size="icon" disabled> action:
<PenBoxIcon className="h-4 w-4 text-muted-foreground" /> authUrl && authUrl !== "#"
</Button> ? {
); label: "Authorize Now",
} onClick: () => window.open(authUrl, "_blank"),
}
: undefined,
});
}
};
return ( // Generate Gitea OAuth URL with state parameter
<Dialog open={open} onOpenChange={setOpen}> const getGiteaOAuthUrl = () => {
<DialogTrigger asChild> const clientId = form.getValues().clientId;
<Button variant="ghost" size="icon" className="group hover:bg-blue-500/10"> const giteaUrl = form.getValues().giteaUrl;
<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"> if (!clientId || !giteaUrl) {
<Button toast.error("Configuration Incomplete", {
type="button" description: "Please fill in Client ID and Gitea URL first.",
variant="outline" });
onClick={handleTestConnection} return "#";
isLoading={isTesting} }
>
Test Connection const redirectUri = `${url}/api/providers/gitea/callback`;
</Button>
// Use the scopes from the gitea data (if available), else fallback to default scopes
<Button const scopes =
type="button" gitea?.scopes?.split(",").join(" ") ||
variant="outline" "repo repo:status read:user read:org";
onClick={() => { //const scopes = gitea?.scopes || 'repo,repo:status,read:user,read:org';
const authUrl = getGiteaOAuthUrl();
if (authUrl !== "#") { const state = giteaId;
window.open(authUrl, "_blank");
} return `${giteaUrl}/login/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scopes)}&state=${encodeURIComponent(state)}`;
}} };
>
Connect to Gitea // Show loading state if data is being fetched
</Button> if (isLoading) {
return (
<Button type="submit" isLoading={isUpdating}> <Button variant="ghost" size="icon" disabled>
Save <PenBoxIcon className="h-4 w-4 text-muted-foreground" />
</Button> </Button>
</div> );
</form> }
</Form>
</DialogContent> return (
</Dialog> <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 { import {
BitbucketIcon, BitbucketIcon,
GiteaIcon,
GithubIcon, GithubIcon,
GitlabIcon, GitlabIcon,
GiteaIcon, } from "@/components/icons/data-tools-icons";
} from "@/components/icons/data-tools-icons"; import { DialogAction } from "@/components/shared/dialog-action";
import { DialogAction } from "@/components/shared/dialog-action"; import { Button, buttonVariants } from "@/components/ui/button";
import { Button, buttonVariants } from "@/components/ui/button"; import {
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; Card,
import { api } from "@/utils/api"; CardContent,
import { useUrl } from "@/utils/hooks/use-url"; CardDescription,
import { formatDate } from "date-fns"; CardHeader,
import { CardTitle,
} from "@/components/ui/card";
import { api } from "@/utils/api";
import { useUrl } from "@/utils/hooks/use-url";
import { formatDate } from "date-fns";
import {
ExternalLinkIcon, ExternalLinkIcon,
GitBranch, GitBranch,
ImportIcon, ImportIcon,
Loader2, Loader2,
Trash2, Trash2,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { toast } from "sonner"; import { toast } from "sonner";
import { AddBitbucketProvider } from "./bitbucket/add-bitbucket-provider"; import { AddBitbucketProvider } from "./bitbucket/add-bitbucket-provider";
import { EditBitbucketProvider } from "./bitbucket/edit-bitbucket-provider"; import { EditBitbucketProvider } from "./bitbucket/edit-bitbucket-provider";
import { AddGithubProvider } from "./github/add-github-provider"; import { AddGiteaProvider } from "./gitea/add-gitea-provider";
import { EditGithubProvider } from "./github/edit-github-provider"; import { EditGiteaProvider } from "./gitea/edit-gitea-provider";
import { AddGitlabProvider } from "./gitlab/add-gitlab-provider"; import { AddGithubProvider } from "./github/add-github-provider";
import { EditGitlabProvider } from "./gitlab/edit-gitlab-provider"; import { EditGithubProvider } from "./github/edit-github-provider";
import { AddGiteaProvider } from "./gitea/add-gitea-provider"; import { AddGitlabProvider } from "./gitlab/add-gitlab-provider";
import { EditGiteaProvider } from "./gitea/edit-gitea-provider"; import { EditGitlabProvider } from "./gitlab/edit-gitlab-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} />
)}
{isGitea && ( export const ShowGitProviders = () => {
<EditGiteaProvider giteaId={gitProvider.gitea?.giteaId} /> const { data, isLoading, refetch } = api.gitProvider.getAll.useQuery();
)} const { mutateAsync, isLoading: isRemoving } =
api.gitProvider.remove.useMutation();
<DialogAction const url = useUrl();
title="Delete Git Provider"
description="Are you sure you want to delete this Git Provider?" const getGitlabUrl = (
type="destructive" clientId: string,
onClick={async () => { gitlabId: string,
await mutateAsync({ gitlabUrl: string,
gitProviderId: gitProvider.gitProviderId, ) => {
}) const redirectUri = `${url}/api/providers/gitlab/callback?gitlabId=${gitlabId}`;
.then(() => { const scope = "api read_user read_repository";
toast.success("Git Provider deleted successfully"); const authUrl = `${gitlabUrl}/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`;
refetch(); return authUrl;
}) };
.catch(() => {
toast.error("Error deleting Git Provider"); 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 ">
<Button <CardHeader className="">
variant="ghost" <CardTitle className="text-xl flex flex-row gap-2">
size="icon" <GitBranch className="size-6 text-muted-foreground self-center" />
className="group hover:bg-red-500/10" Git Providers
isLoading={isRemoving} </CardTitle>
> <CardDescription>
<Trash2 className="size-4 text-primary group-hover:text-red-500" /> Connect your Git provider for authentication.
</Button> </CardDescription>
</DialogAction> </CardHeader>
</div> <CardContent className="space-y-2 py-8 border-t">
</div> {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> {data?.length === 0 ? (
<div className="flex flex-col items-center gap-3 min-h-[25vh] justify-center">
<div className="flex flex-row gap-2 flex-wrap w-full justify-end mr-4"> <GitBranch className="size-8 self-center text-muted-foreground" />
{/* <AddCertificate /> */} <span className="text-base text-muted-foreground text-center">
</div> Create your first Git Provider
</div> </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">
</CardContent> <AddGithubProvider />
</div> <AddGitlabProvider />
</Card> <AddBitbucketProvider />
</div> <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 Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { toast } from "sonner"; import { toast } from "sonner";
import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal";
import { TerminalModal } from "../web-server/terminal-modal"; import { TerminalModal } from "../web-server/terminal-modal";
import { ShowServerActions } from "./actions/show-server-actions"; import { ShowServerActions } from "./actions/show-server-actions";
import { HandleServers } from "./handle-servers"; import { HandleServers } from "./handle-servers";
@@ -42,7 +43,6 @@ import { ShowMonitoringModal } from "./show-monitoring-modal";
import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal"; import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal";
import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal"; import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal";
import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription"; import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription";
import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal";
export const ShowServers = () => { export const ShowServers = () => {
const { t } = useTranslation("settings"); const { t } = useTranslation("settings");

View File

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

View File

@@ -12,7 +12,7 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { DatabaseIcon, AlertTriangle } from "lucide-react"; import { AlertTriangle, DatabaseIcon } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
interface Props { 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 { ShowResources } from "@/components/dashboard/application/advanced/show-resources";
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes"; 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"; import { RebuildDatabase } from "./rebuild-database";
interface Props { interface Props {

View File

@@ -241,34 +241,34 @@ export const BitbucketIcon = ({ className }: Props) => {
export const GiteaIcon = ({ className }: Props) => { export const GiteaIcon = ({ className }: Props) => {
return ( return (
<svg <svg
className={className} className={className}
version="1.1" version="1.1"
id="main_outline" id="main_outline"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="18" width="18"
height="18" height="18"
viewBox="5.67 143.05 628.65 387.55" viewBox="5.67 143.05 628.65 387.55"
enableBackground="new 0 0 640 640" 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> <g>
<path <path
style={{ fill: '#609926' }} id="teabag"
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" 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"
<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>
<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>
</g>
</svg> </svg>
); );
}; };

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
import type * as React from "react";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import type * as React from "react";
import { DayPicker } from "react-day-picker"; import { DayPicker } from "react-day-picker";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export type CalendarProps = React.ComponentProps<typeof DayPicker>; 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", "id": "7c8508ba-01eb-487e-88cf-0abe10916904",
"prevId": "dd51ff04-a160-4d0d-b72f-4916493b740f", "prevId": "f74127dc-3cda-4091-988f-97b60eb22427",
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",
"tables": { "tables": {
@@ -179,6 +179,13 @@
"notNull": true, "notNull": true,
"default": "'github'" "default": "'github'"
}, },
"cleanCache": {
"name": "cleanCache",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"repository": { "repository": {
"name": "repository", "name": "repository",
"type": "text", "type": "text",
@@ -247,6 +254,43 @@
"primaryKey": false, "primaryKey": false,
"notNull": 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": { "bitbucketRepository": {
"name": "bitbucketRepository", "name": "bitbucketRepository",
"type": "text", "type": "text",
@@ -458,6 +502,12 @@
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
}, },
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketId": { "bitbucketId": {
"name": "bitbucketId", "name": "bitbucketId",
"type": "text", "type": "text",
@@ -538,6 +588,19 @@
"onDelete": "set null", "onDelete": "set null",
"onUpdate": "no action" "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": { "application_bitbucketId_bitbucket_bitbucketId_fk": {
"name": "application_bitbucketId_bitbucket_bitbucketId_fk", "name": "application_bitbucketId_bitbucket_bitbucketId_fk",
"tableFrom": "application", "tableFrom": "application",
@@ -1703,6 +1766,12 @@
"primaryKey": false, "primaryKey": false,
"notNull": true "notNull": true
}, },
"keepLatestCount": {
"name": "keepLatestCount",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"databaseType": { "databaseType": {
"name": "databaseType", "name": "databaseType",
"type": "databaseType", "type": "databaseType",
@@ -2908,6 +2977,24 @@
"primaryKey": false, "primaryKey": false,
"notNull": 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": { "customGitUrl": {
"name": "customGitUrl", "name": "customGitUrl",
"type": "text", "type": "text",
@@ -3005,6 +3092,12 @@
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
}, },
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"serverId": { "serverId": {
"name": "serverId", "name": "serverId",
"type": "text", "type": "text",
@@ -3079,6 +3172,19 @@
"onDelete": "set null", "onDelete": "set null",
"onUpdate": "no action" "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": { "compose_serverId_server_serverId_fk": {
"name": "compose_serverId_server_serverId_fk", "name": "compose_serverId_server_serverId_fk",
"tableFrom": "compose", "tableFrom": "compose",
@@ -3937,6 +4043,107 @@
"checkConstraints": {}, "checkConstraints": {},
"isRLSEnabled": false "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": { "public.server": {
"name": "server", "name": "server",
"schema": "", "schema": "",
@@ -5001,6 +5208,7 @@
"github", "github",
"gitlab", "gitlab",
"bitbucket", "bitbucket",
"gitea",
"drop" "drop"
] ]
}, },
@@ -5097,6 +5305,7 @@
"github", "github",
"gitlab", "gitlab",
"bitbucket", "bitbucket",
"gitea",
"raw" "raw"
] ]
}, },
@@ -5125,7 +5334,8 @@
"values": [ "values": [
"github", "github",
"gitlab", "gitlab",
"bitbucket" "bitbucket",
"gitea"
] ]
}, },
"public.serverStatus": { "public.serverStatus": {

View File

@@ -1,6 +1,6 @@
{ {
"id": "d4d95ab9-16bd-4583-949e-d6ae0a0bb7a0", "id": "c8f3ee9b-66c6-4a23-a151-78e04363bdf9",
"prevId": "27ba0f3d-859f-4233-a179-8aee11ad9179", "prevId": "f74127dc-3cda-4091-988f-97b60eb22427",
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",
"tables": { "tables": {
@@ -179,6 +179,13 @@
"notNull": true, "notNull": true,
"default": "'github'" "default": "'github'"
}, },
"cleanCache": {
"name": "cleanCache",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"repository": { "repository": {
"name": "repository", "name": "repository",
"type": "text", "type": "text",
@@ -247,6 +254,43 @@
"primaryKey": false, "primaryKey": false,
"notNull": 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": { "bitbucketRepository": {
"name": "bitbucketRepository", "name": "bitbucketRepository",
"type": "text", "type": "text",
@@ -458,6 +502,12 @@
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
}, },
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketId": { "bitbucketId": {
"name": "bitbucketId", "name": "bitbucketId",
"type": "text", "type": "text",
@@ -538,6 +588,19 @@
"onDelete": "set null", "onDelete": "set null",
"onUpdate": "no action" "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": { "application_bitbucketId_bitbucket_bitbucketId_fk": {
"name": "application_bitbucketId_bitbucket_bitbucketId_fk", "name": "application_bitbucketId_bitbucket_bitbucketId_fk",
"tableFrom": "application", "tableFrom": "application",
@@ -2914,6 +2977,24 @@
"primaryKey": false, "primaryKey": false,
"notNull": 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": { "customGitUrl": {
"name": "customGitUrl", "name": "customGitUrl",
"type": "text", "type": "text",
@@ -3011,6 +3092,12 @@
"primaryKey": false, "primaryKey": false,
"notNull": false "notNull": false
}, },
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"serverId": { "serverId": {
"name": "serverId", "name": "serverId",
"type": "text", "type": "text",
@@ -3085,6 +3172,19 @@
"onDelete": "set null", "onDelete": "set null",
"onUpdate": "no action" "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": { "compose_serverId_server_serverId_fk": {
"name": "compose_serverId_server_serverId_fk", "name": "compose_serverId_server_serverId_fk",
"tableFrom": "compose", "tableFrom": "compose",
@@ -3943,6 +4043,107 @@
"checkConstraints": {}, "checkConstraints": {},
"isRLSEnabled": false "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": { "public.server": {
"name": "server", "name": "server",
"schema": "", "schema": "",
@@ -5007,6 +5208,7 @@
"github", "github",
"gitlab", "gitlab",
"bitbucket", "bitbucket",
"gitea",
"drop" "drop"
] ]
}, },
@@ -5103,6 +5305,7 @@
"github", "github",
"gitlab", "gitlab",
"bitbucket", "bitbucket",
"gitea",
"raw" "raw"
] ]
}, },
@@ -5131,7 +5334,8 @@
"values": [ "values": [
"github", "github",
"gitlab", "gitlab",
"bitbucket" "bitbucket",
"gitea"
] ]
}, },
"public.serverStatus": { "public.serverStatus": {

View File

@@ -534,10 +534,31 @@
"tag": "0075_wild_xorn", "tag": "0075_wild_xorn",
"breakpoints": true "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, "idx": 78,
"version": "7", "version": "7",
"when": 1742112194375, "when": 1742241730001,
"tag": "0077_chemical_dreadnoughts",
"breakpoints": true
},
{
"idx": 79,
"version": "7",
"when": 17422417300002,
"tag": "0078_uneven_omega_sentinel", "tag": "0078_uneven_omega_sentinel",
"breakpoints": true "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} */ /** @type {import("next").NextConfig} */
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
eslint: { eslint: {
ignoreDuringBuilds: true, ignoreDuringBuilds: true,
}, },
typescript: { typescript: {
ignoreBuildErrors: true, ignoreBuildErrors: true,
}, },
transpilePackages: ["@dokploy/server"], transpilePackages: ["@dokploy/server"],
/** /**
* If you are using `appDir` then you must comment the below `i18n` config out. * If you are using `appDir` then you must comment the below `i18n` config out.
* *
* @see https://github.com/vercel/next.js/issues/41980 * @see https://github.com/vercel/next.js/issues/41980
*/ */
i18n: { i18n: {
locales: ["en"], locales: ["en"],
defaultLocale: "en", defaultLocale: "en",
}, },
}; };
export default nextConfig; export default nextConfig;

View File

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

View File

@@ -112,13 +112,6 @@ export default async function handler(
res.status(301).json({ message: "Branch Not Match" }); res.status(301).json({ message: "Branch Not Match" });
return; 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( const commitedPaths = await extractCommitedPaths(
req.body, req.body,
@@ -134,7 +127,12 @@ export default async function handler(
if (!shouldDeployPaths) { if (!shouldDeployPaths) {
res.status(301).json({ message: "Watch Paths Not Match" }); res.status(301).json({ message: "Watch Paths Not Match" });
return; 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 type { NextApiRequest, NextApiResponse } from "next";
import { import {
extractBranchName, extractBranchName,
extractCommitedPaths,
extractCommitMessage, extractCommitMessage,
extractCommitedPaths,
extractHash, extractHash,
} from "../[refreshToken]"; } from "../[refreshToken]";

View File

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

View File

@@ -1,186 +1,94 @@
import { NextApiRequest, NextApiResponse } from 'next'; import { updateGitea } from "@dokploy/server";
import { findGiteaById, 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( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
try { const { code, state } = req.query;
console.log('Full Callback Request:', {
query: req.query,
method: req.method,
timestamp: new Date().toISOString()
});
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 const giteaId = parseState(state as string);
console.log('Received Parameters:', { if (!giteaId) return redirectWithError(res, "Invalid state format");
code: code ? 'Present' : 'Missing',
state: state ? 'Present' : 'Missing'
});
if (!code || Array.isArray(code)) { const gitea = await findGitea(giteaId);
console.error('Invalid code:', code); if (!gitea) return redirectWithError(res, "Failed to find Gitea provider");
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Invalid authorization code')}`
);
}
// The state parameter now contains the giteaId // Fetch the access token from Gitea
if (!state || Array.isArray(state)) { const result = await fetchAccessToken(gitea, code as string);
console.error('Invalid state parameter:', state);
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Invalid state parameter')}`
);
}
// Extract the giteaId from the state parameter if (result.error) {
let giteaId: string; console.error("Token exchange failed:", result);
try { return redirectWithError(res, result.error);
// 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 (!giteaId) { if (!result.access_token) {
throw new Error('giteaId not found in state parameter'); console.error("Missing access token:", result);
} return redirectWithError(res, "No access token received");
} catch (parseError) { }
console.error('Error parsing state parameter:', parseError);
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Invalid state format')}`
);
}
let gitea; const expiresAt = result.expires_in
try { ? Math.floor(Date.now() / 1000) + result.expires_in
gitea = await findGiteaById(giteaId); : null;
} catch (findError) {
console.error('Error finding Gitea provider:', findError);
return res.redirect(
307,
`/dashboard/settings/git-providers?error=${encodeURIComponent('Failed to find Gitea provider')}`
);
}
// Extensive logging of Gitea provider details try {
console.log('Gitea Provider Details:', { const updatedGitea = await updateGitea(gitea.giteaId, {
id: gitea.giteaId, accessToken: result.access_token,
url: gitea.giteaUrl, refreshToken: result.refresh_token,
clientId: gitea.clientId ? 'Present' : 'Missing', expiresAt,
clientSecret: gitea.clientSecret ? 'Present' : 'Missing', ...(result.organizationName
redirectUri: gitea.redirectUri ? { organizationName: result.organizationName }
}); : {}),
});
// Validate required OAuth parameters console.log("Gitea provider updated successfully:", updatedGitea);
if (!gitea.clientId || !gitea.clientSecret) { return res.redirect(
console.error('Missing OAuth configuration:', { 307,
hasClientId: !!gitea.clientId, "/dashboard/settings/git-providers?connected=true",
hasClientSecret: !!gitea.clientSecret );
}); } catch (updateError) {
return res.redirect( console.error("Failed to update Gitea provider:", updateError);
307, return redirectWithError(res, "Failed to store access token");
`/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')}`
);
}
}

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, CommandInput,
CommandItem, CommandItem,
} from "@/components/ui/command"; } from "@/components/ui/command";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -47,6 +56,13 @@ import {
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root"; import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
@@ -64,8 +80,8 @@ import {
Loader2, Loader2,
PlusIcon, PlusIcon,
Search, Search,
X,
Trash2, Trash2,
X,
} from "lucide-react"; } from "lucide-react";
import type { import type {
GetServerSidePropsContext, GetServerSidePropsContext,
@@ -73,25 +89,9 @@ import type {
} from "next"; } from "next";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; 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 { toast } from "sonner";
import superjson from "superjson"; 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 = { export type Services = {
appName: string; 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 { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment"; import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command"; 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 { type ReactElement, useEffect, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import superjson from "superjson"; import superjson from "superjson";
import { ShowImport } from "@/components/dashboard/application/advanced/import/show-import";
type TabState = type TabState =
| "projects" | "projects"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,23 +9,10 @@ import {
apiUpdateCompose, apiUpdateCompose,
compose as composeTable, compose as composeTable,
} from "@/server/db/schema"; } 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 type { DeploymentJob } from "@/server/queues/queue-types";
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
import { deploy } from "@/server/utils/deploy"; import { deploy } from "@/server/utils/deploy";
import { generatePassword } from "@/templates/utils";
import { import {
IS_CLOUD, IS_CLOUD,
addDomainToCompose, addDomainToCompose,
@@ -55,6 +42,19 @@ import {
stopCompose, stopCompose,
updateCompose, updateCompose,
} from "@dokploy/server"; } 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({ export const composeRouter = createTRPCRouter({
create: protectedProcedure create: protectedProcedure

View File

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

View File

@@ -1,238 +1,240 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { import {
apiCreateGitea, apiCreateGitea,
apiFindOneGitea, apiFindGiteaBranches,
apiGiteaTestConnection, apiFindOneGitea,
apiUpdateGitea, apiGiteaTestConnection,
apiFindGiteaBranches, apiUpdateGitea,
} from "@/server/db/schema"; } from "@/server/db/schema";
import { db } from "@/server/db"; import { db } from "@/server/db";
import { import {
createGitea, createGitea,
findGiteaById, findGiteaById,
haveGiteaRequirements, getGiteaBranches,
testGiteaConnection, getGiteaRepositories,
updateGitProvider, haveGiteaRequirements,
updateGitea, testGiteaConnection,
getGiteaBranches, updateGitProvider,
getGiteaRepositories, updateGitea,
} from "@dokploy/server"; } from "@dokploy/server";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
// Gitea Router // Gitea Router
export const giteaRouter = createTRPCRouter({ export const giteaRouter = createTRPCRouter({
// Create a new Gitea provider // Create a new Gitea provider
create: protectedProcedure create: protectedProcedure
.input(apiCreateGitea) .input(apiCreateGitea)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
try { try {
return await createGitea(input, ctx.session.activeOrganizationId); return await createGitea(input, ctx.session.activeOrganizationId);
} catch (error) { } catch (error) {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: "Error creating this Gitea provider", message: "Error creating this Gitea provider",
cause: error, cause: error,
}); });
} }
}), }),
// Fetch a specific Gitea provider by ID // Fetch a specific Gitea provider by ID
one: protectedProcedure one: protectedProcedure
.input(apiFindOneGitea) .input(apiFindOneGitea)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
const giteaProvider = await findGiteaById(input.giteaId); const giteaProvider = await findGiteaById(input.giteaId);
if ( if (
giteaProvider.gitProvider.organizationId !== giteaProvider.gitProvider.organizationId !==
ctx.session.activeOrganizationId ctx.session.activeOrganizationId
) { ) {
throw new TRPCError({ throw new TRPCError({
code: "UNAUTHORIZED", code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider", message: "You are not allowed to access this Gitea provider",
}); });
} }
return giteaProvider; return giteaProvider;
}), }),
// Fetch all Gitea providers for the active organization // Fetch all Gitea providers for the active organization
giteaProviders: protectedProcedure.query(async ({ ctx }) => { giteaProviders: protectedProcedure.query(async ({ ctx }) => {
let result = await db.query.gitea.findMany({ let result = await db.query.gitea.findMany({
with: { with: {
gitProvider: true, gitProvider: true,
}, },
}); });
// Filter by organization ID // Filter by organization ID
result = result.filter( result = result.filter(
(provider) => (provider) =>
provider.gitProvider.organizationId === ctx.session.activeOrganizationId 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,
},
};
});
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 return filtered;
getGiteaRepositories: protectedProcedure }),
.input(apiFindOneGitea)
.query(async ({ input, ctx }) => {
const { giteaId } = input;
if (!giteaId) { // Fetch repositories from Gitea provider
throw new TRPCError({ getGiteaRepositories: protectedProcedure
code: "BAD_REQUEST", .input(apiFindOneGitea)
message: "Gitea provider ID is required.", .query(async ({ input, ctx }) => {
}); const { giteaId } = input;
}
const giteaProvider = await findGiteaById(giteaId); if (!giteaId) {
if ( throw new TRPCError({
giteaProvider.gitProvider.organizationId !== code: "BAD_REQUEST",
ctx.session.activeOrganizationId message: "Gitea provider ID is required.",
) { });
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),
});
}
}),
// Fetch branches of a specific Gitea repository const giteaProvider = await findGiteaById(giteaId);
getGiteaBranches: protectedProcedure if (
.input(apiFindGiteaBranches) giteaProvider.gitProvider.organizationId !==
.query(async ({ input, ctx }) => { ctx.session.activeOrganizationId
const { giteaId, owner, repositoryName } = input; ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitea provider",
});
}
if (!giteaId || !owner || !repositoryName) { try {
throw new TRPCError({ // Call the service layer function to get repositories
code: "BAD_REQUEST", console.log("Calling getGiteaRepositories with giteaId:", giteaId);
message: "Gitea provider ID, owner, and repository name are required.", 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); // Fetch branches of a specific Gitea repository
if ( getGiteaBranches: protectedProcedure
giteaProvider.gitProvider.organizationId !== .input(apiFindGiteaBranches)
ctx.session.activeOrganizationId .query(async ({ input, ctx }) => {
) { const { giteaId, owner, repositoryName } = input;
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),
});
}
}),
// Test connection to Gitea provider if (!giteaId || !owner || !repositoryName) {
testConnection: protectedProcedure throw new TRPCError({
.input(apiGiteaTestConnection) code: "BAD_REQUEST",
.mutation(async ({ input, ctx }) => { message:
// Ensure giteaId is always a non-empty string "Gitea provider ID, owner, and repository name are required.",
const giteaId = input.giteaId ?? ''; });
}
try { const giteaProvider = await findGiteaById(giteaId);
const giteaProvider = await findGiteaById(giteaId); if (
if ( giteaProvider.gitProvider.organizationId !==
giteaProvider.gitProvider.organizationId !== ctx.session.activeOrganizationId
ctx.session.activeOrganizationId ) {
) { throw new TRPCError({
throw new TRPCError({ code: "UNAUTHORIZED",
code: "UNAUTHORIZED", message: "You are not allowed to access this Gitea provider",
message: "You are not allowed to access this Gitea provider", });
}); }
}
const result = await testGiteaConnection({
giteaId,
});
return `Found ${result} repositories`; try {
} catch (error) { // Call the service layer function with the required parameters
console.error('Gitea connection test error:', error); console.log("Calling getGiteaBranches with:", {
throw new TRPCError({ giteaId,
code: "BAD_REQUEST", owner,
message: error instanceof Error ? error.message : String(error), repo: repositoryName,
}); });
}
}),
// Update an existing Gitea provider return await getGiteaBranches({
update: protectedProcedure giteaId,
.input(apiUpdateGitea) owner,
.mutation(async ({ input, ctx }) => { repo: repositoryName,
const giteaProvider = await findGiteaById(input.giteaId); id: 0, // Provide a default value for the optional id
if ( });
giteaProvider.gitProvider.organizationId !== } catch (error) {
ctx.session.activeOrganizationId console.error("Error fetching Gitea branches:", error);
) { throw new TRPCError({
throw new TRPCError({ code: "BAD_REQUEST",
code: "UNAUTHORIZED", message: error instanceof Error ? error.message : String(error),
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, { // Test connection to Gitea provider
...input, testConnection: protectedProcedure
}); .input(apiGiteaTestConnection)
} else { .mutation(async ({ input, ctx }) => {
await updateGitea(input.giteaId, { // Ensure giteaId is always a non-empty string
...input, const giteaId = input.giteaId ?? "";
});
} try {
const giteaProvider = await findGiteaById(giteaId);
return { success: true }; 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 { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { db } from "@/server/db";
import { import {
apiChangeMariaDBStatus, apiChangeMariaDBStatus,
apiCreateMariaDB, apiCreateMariaDB,
apiDeployMariaDB, apiDeployMariaDB,
apiFindOneMariaDB, apiFindOneMariaDB,
apiRebuildMariadb,
apiResetMariadb, apiResetMariadb,
apiSaveEnvironmentVariablesMariaDB, apiSaveEnvironmentVariablesMariaDB,
apiSaveExternalPortMariaDB, apiSaveExternalPortMariaDB,
apiUpdateMariaDB, apiUpdateMariaDB,
apiRebuildMariadb,
mariadb as mariadbTable, mariadb as mariadbTable,
} from "@/server/db/schema"; } from "@/server/db/schema";
import { cancelJobs } from "@/server/utils/backup"; import { cancelJobs } from "@/server/utils/backup";
@@ -30,12 +31,11 @@ import {
stopServiceRemote, stopServiceRemote,
updateMariadbById, updateMariadbById,
} from "@dokploy/server"; } from "@dokploy/server";
import { rebuildDatabase } from "@dokploy/server";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable"; import { observable } from "@trpc/server/observable";
import { z } from "zod";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { db } from "@/server/db"; import { z } from "zod";
import { rebuildDatabase } from "@dokploy/server";
export const mariadbRouter = createTRPCRouter({ export const mariadbRouter = createTRPCRouter({
create: protectedProcedure create: protectedProcedure
.input(apiCreateMariaDB) .input(apiCreateMariaDB)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,9 +13,9 @@ import { z } from "zod";
import { bitbucket } from "./bitbucket"; import { bitbucket } from "./bitbucket";
import { deployments } from "./deployment"; import { deployments } from "./deployment";
import { domains } from "./domain"; import { domains } from "./domain";
import { gitea } from "./gitea";
import { github } from "./github"; import { github } from "./github";
import { gitlab } from "./gitlab"; import { gitlab } from "./gitlab";
import { gitea } from "./gitea";
import { mounts } from "./mount"; import { mounts } from "./mount";
import { ports } from "./port"; import { ports } from "./port";
import { previewDeployments } from "./preview-deployments"; import { previewDeployments } from "./preview-deployments";
@@ -395,7 +395,9 @@ const createSchema = createInsertSchema(applications, {
customGitUrl: z.string().optional(), customGitUrl: z.string().optional(),
buildPath: z.string().optional(), buildPath: z.string().optional(),
projectId: z.string(), 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"]), applicationStatus: z.enum(["idle", "running", "done", "error"]),
buildType: z.enum([ buildType: z.enum([
"dockerfile", "dockerfile",
@@ -556,4 +558,4 @@ export const apiUpdateApplication = createSchema
.extend({ .extend({
applicationId: z.string().min(1), 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 { bitbucket } from "./bitbucket";
import { deployments } from "./deployment"; import { deployments } from "./deployment";
import { domains } from "./domain"; import { domains } from "./domain";
import { gitea } from "./gitea";
import { github } from "./github"; import { github } from "./github";
import { gitlab } from "./gitlab"; import { gitlab } from "./gitlab";
import { mounts } from "./mount"; import { mounts } from "./mount";
@@ -14,7 +15,6 @@ import { server } from "./server";
import { applicationStatus } from "./shared"; import { applicationStatus } from "./shared";
import { sshKeys } from "./ssh-key"; import { sshKeys } from "./ssh-key";
import { generateAppName } from "./utils"; import { generateAppName } from "./utils";
import { gitea } from "./gitea";
export const sourceTypeCompose = pgEnum("sourceTypeCompose", [ export const sourceTypeCompose = pgEnum("sourceTypeCompose", [
"git", "git",
@@ -58,9 +58,9 @@ export const compose = pgTable("compose", {
bitbucketOwner: text("bitbucketOwner"), bitbucketOwner: text("bitbucketOwner"),
bitbucketBranch: text("bitbucketBranch"), bitbucketBranch: text("bitbucketBranch"),
// Gitea // Gitea
giteaRepository: text("giteaRepository"), giteaRepository: text("giteaRepository"),
giteaOwner: text("giteaOwner"), giteaOwner: text("giteaOwner"),
giteaBranch: text("giteaBranch"), giteaBranch: text("giteaBranch"),
// Git // Git
customGitUrl: text("customGitUrl"), customGitUrl: text("customGitUrl"),
customGitBranch: text("customGitBranch"), customGitBranch: text("customGitBranch"),
@@ -94,8 +94,8 @@ export const compose = pgTable("compose", {
onDelete: "set null", onDelete: "set null",
}), }),
giteaId: text("giteaId").references(() => gitea.giteaId, { giteaId: text("giteaId").references(() => gitea.giteaId, {
onDelete: "set null", onDelete: "set null",
}), }),
serverId: text("serverId").references(() => server.serverId, { serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade", onDelete: "cascade",
}), }),
@@ -126,9 +126,9 @@ export const composeRelations = relations(compose, ({ one, many }) => ({
references: [bitbucket.bitbucketId], references: [bitbucket.bitbucketId],
}), }),
gitea: one(gitea, { gitea: one(gitea, {
fields: [compose.giteaId], fields: [compose.giteaId],
references: [gitea.giteaId], references: [gitea.giteaId],
}), }),
server: one(server, { server: one(server, {
fields: [compose.serverId], fields: [compose.serverId],
references: [server.serverId], references: [server.serverId],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import { paths } from "@dokploy/server/constants"; import { paths } from "@dokploy/server/constants";
import { execAsync } from "../process/execAsync";
import { findAdmin } from "@dokploy/server/services/admin"; import { findAdmin } from "@dokploy/server/services/admin";
import { updateUser } from "@dokploy/server/services/user"; import { updateUser } from "@dokploy/server/services/user";
import { scheduleJob, scheduledJobs } from "node-schedule"; import { scheduleJob, scheduledJobs } from "node-schedule";
import { execAsync } from "../process/execAsync";
const LOG_CLEANUP_JOB_NAME = "access-log-cleanup"; 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 { getAllServers } from "@dokploy/server/services/server";
import { scheduleJob } from "node-schedule"; import { scheduleJob } from "node-schedule";
import { db } from "../../db/index"; import { db } from "../../db/index";
import { findAdmin } from "../../services/admin";
import { import {
cleanUpDockerBuilder, cleanUpDockerBuilder,
cleanUpSystemPrune, cleanUpSystemPrune,
cleanUpUnusedImages, cleanUpUnusedImages,
} from "../docker/utils"; } from "../docker/utils";
import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup"; import { sendDockerCleanupNotifications } from "../notifications/docker-cleanup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import { runMariadbBackup } from "./mariadb"; import { runMariadbBackup } from "./mariadb";
import { runMongoBackup } from "./mongo"; import { runMongoBackup } from "./mongo";
import { runMySqlBackup } from "./mysql"; import { runMySqlBackup } from "./mysql";
import { runPostgresBackup } from "./postgres"; import { runPostgresBackup } from "./postgres";
import { findAdmin } from "../../services/admin";
import { getS3Credentials } from "./utils"; import { getS3Credentials } from "./utils";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import type { BackupSchedule } from "@dokploy/server/services/backup"; import type { BackupSchedule } from "@dokploy/server/services/backup";
import { startLogCleanup } from "../access-log/handler"; import { startLogCleanup } from "../access-log/handler";

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
import { createHash } from "node:crypto";
import type { WriteStream } from "node:fs"; import type { WriteStream } from "node:fs";
import { nanoid } from "nanoid";
import type { ApplicationNested } from "."; import type { ApplicationNested } from ".";
import { prepareEnvironmentVariables } from "../docker/utils"; import { prepareEnvironmentVariables } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory"; import { getBuildAppDirectory } from "../filesystem/directory";
import { spawnAsync } from "../process/spawnAsync";
import { execAsync } from "../process/execAsync"; import { execAsync } from "../process/execAsync";
import { nanoid } from "nanoid"; import { spawnAsync } from "../process/spawnAsync";
import { createHash } from "node:crypto";
const calculateSecretsHash = (envVariables: string[]): string => { const calculateSecretsHash = (envVariables: string[]): string => {
const hash = createHash("sha256"); 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 { db } from "@dokploy/server/db";
import { import {
postgres,
mysql,
mariadb, mariadb,
mongo, mongo,
mysql,
postgres,
redis, redis,
} from "@dokploy/server/db/schema"; } 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 { 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"; type DatabaseType = "postgres" | "mysql" | "mariadb" | "mongo" | "redis";

View File

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