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" />

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

@@ -20,7 +20,14 @@ 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;

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,10 +286,15 @@ 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>

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

@@ -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

@@ -20,11 +20,11 @@ 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"),
@@ -40,9 +40,14 @@ interface Props {
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 {
data: gitea,
isLoading,
refetch,
} = api.gitea.one.useQuery({ giteaId });
const { mutateAsync, isLoading: isUpdating } = api.gitea.update.useMutation(); const { mutateAsync, isLoading: isUpdating } = api.gitea.update.useMutation();
const { mutateAsync: testConnection, isLoading: isTesting } = api.gitea.testConnection.useMutation(); const { mutateAsync: testConnection, isLoading: isTesting } =
api.gitea.testConnection.useMutation();
const url = useUrl(); const url = useUrl();
const utils = api.useUtils(); const utils = api.useUtils();
@@ -56,26 +61,34 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
if (connected) { if (connected) {
toast.success("Successfully connected to Gitea", { toast.success("Successfully connected to Gitea", {
description: "Your Gitea provider has been authorized.", description: "Your Gitea provider has been authorized.",
id: 'gitea-connection-success' id: "gitea-connection-success",
}); });
refetch(); refetch();
// Clear the query parameters to prevent re-triggering // Clear the query parameters to prevent re-triggering
router.replace({ router.replace(
{
pathname: router.pathname, pathname: router.pathname,
query: {} query: {},
}, undefined, { shallow: true }); },
undefined,
{ shallow: true },
);
} }
if (error) { if (error) {
toast.error("Gitea Connection Failed", { toast.error("Gitea Connection Failed", {
description: decodeURIComponent(error as string), description: decodeURIComponent(error as string),
id: 'gitea-connection-error' id: "gitea-connection-error",
}); });
// Clear the query parameters to prevent re-triggering // Clear the query parameters to prevent re-triggering
router.replace({ router.replace(
{
pathname: router.pathname, pathname: router.pathname,
query: {} query: {},
}, undefined, { shallow: true }); },
undefined,
{ shallow: true },
);
} }
}, [router.query, router.isReady, refetch]); }, [router.query, router.isReady, refetch]);
@@ -125,17 +138,21 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
try { try {
const result = await testConnection({ giteaId }); const result = await testConnection({ giteaId });
toast.success("Gitea Connection Verified", { toast.success("Gitea Connection Verified", {
description: result description: result,
}); });
} catch (error: any) { } catch (error: any) {
const authUrl = error.authorizationUrl || getGiteaOAuthUrl(); const authUrl = error.authorizationUrl || getGiteaOAuthUrl();
toast.error("Gitea Not Connected", { toast.error("Gitea Not Connected", {
description: error.message || "Please complete the OAuth authorization process.", description:
action: authUrl && authUrl !== "#" ? { error.message || "Please complete the OAuth authorization process.",
action:
authUrl && authUrl !== "#"
? {
label: "Authorize Now", label: "Authorize Now",
onClick: () => window.open(authUrl, "_blank") onClick: () => window.open(authUrl, "_blank"),
} : undefined, }
: undefined,
}); });
} }
}; };
@@ -147,7 +164,7 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
if (!clientId || !giteaUrl) { if (!clientId || !giteaUrl) {
toast.error("Configuration Incomplete", { toast.error("Configuration Incomplete", {
description: "Please fill in Client ID and Gitea URL first." description: "Please fill in Client ID and Gitea URL first.",
}); });
return "#"; return "#";
} }
@@ -155,7 +172,9 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
const redirectUri = `${url}/api/providers/gitea/callback`; const redirectUri = `${url}/api/providers/gitea/callback`;
// Use the scopes from the gitea data (if available), else fallback to default scopes // 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?.split(",").join(" ") ||
"repo repo:status read:user read:org";
//const scopes = gitea?.scopes || 'repo,repo:status,read:user,read:org'; //const scopes = gitea?.scopes || 'repo,repo:status,read:user,read:org';
const state = giteaId; const state = giteaId;
@@ -175,7 +194,11 @@ export const EditGiteaProvider = ({ giteaId }: Props) => {
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost" size="icon" className="group hover:bg-blue-500/10"> <Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10"
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" /> <PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>

View File

@@ -1,36 +1,43 @@
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 = () => { export const ShowGitProviders = () => {
const { data, isLoading, refetch } = api.gitProvider.getAll.useQuery(); const { data, isLoading, refetch } = api.gitProvider.getAll.useQuery();
const { mutateAsync, isLoading: isRemoving } = api.gitProvider.remove.useMutation(); const { mutateAsync, isLoading: isRemoving } =
api.gitProvider.remove.useMutation();
const url = useUrl(); const url = useUrl();
const getGitlabUrl = ( const getGitlabUrl = (
@@ -44,17 +51,6 @@ import {
return authUrl; 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 ( return (
<div className="w-full"> <div className="w-full">
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto"> <Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto">
@@ -113,19 +109,23 @@ import {
{data?.map((gitProvider, _index) => { {data?.map((gitProvider, _index) => {
const isGithub = gitProvider.providerType === "github"; const isGithub = gitProvider.providerType === "github";
const isGitlab = gitProvider.providerType === "gitlab"; const isGitlab = gitProvider.providerType === "gitlab";
const isBitbucket = gitProvider.providerType === "bitbucket"; const isBitbucket =
gitProvider.providerType === "bitbucket";
const isGitea = gitProvider.providerType === "gitea"; const isGitea = gitProvider.providerType === "gitea";
const haveGithubRequirements = isGithub && const haveGithubRequirements =
isGithub &&
gitProvider.github?.githubPrivateKey && gitProvider.github?.githubPrivateKey &&
gitProvider.github?.githubAppId && gitProvider.github?.githubAppId &&
gitProvider.github?.githubInstallationId; gitProvider.github?.githubInstallationId;
const haveGitlabRequirements = isGitlab && const haveGitlabRequirements =
isGitlab &&
gitProvider.gitlab?.accessToken && gitProvider.gitlab?.accessToken &&
gitProvider.gitlab?.refreshToken; gitProvider.gitlab?.refreshToken;
const haveGiteaRequirements = isGitea && const haveGiteaRequirements =
isGitea &&
gitProvider.gitea?.accessToken && gitProvider.gitea?.accessToken &&
gitProvider.gitea?.refreshToken; gitProvider.gitea?.refreshToken;
@@ -146,9 +146,7 @@ import {
{isBitbucket && ( {isBitbucket && (
<BitbucketIcon className="size-5" /> <BitbucketIcon className="size-5" />
)} )}
{isGitea && ( {isGitea && <GiteaIcon className="size-5" />}
<GiteaIcon className="size-5" />
)}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<span className="text-sm font-medium"> <span className="text-sm font-medium">
{gitProvider.name} {gitProvider.name}
@@ -156,7 +154,7 @@ import {
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
{formatDate( {formatDate(
gitProvider.createdAt, gitProvider.createdAt,
"yyyy-MM-dd hh:mm:ss a" "yyyy-MM-dd hh:mm:ss a",
)} )}
</span> </span>
</div> </div>
@@ -210,19 +208,29 @@ import {
</div> </div>
)} )}
{isGithub && haveGithubRequirements && ( {isGithub && haveGithubRequirements && (
<EditGithubProvider githubId={gitProvider.github?.githubId} /> <EditGithubProvider
githubId={gitProvider.github?.githubId}
/>
)} )}
{isGitlab && ( {isGitlab && (
<EditGitlabProvider gitlabId={gitProvider.gitlab?.gitlabId} /> <EditGitlabProvider
gitlabId={gitProvider.gitlab?.gitlabId}
/>
)} )}
{isBitbucket && ( {isBitbucket && (
<EditBitbucketProvider bitbucketId={gitProvider.bitbucket?.bitbucketId} /> <EditBitbucketProvider
bitbucketId={
gitProvider.bitbucket?.bitbucketId
}
/>
)} )}
{isGitea && ( {isGitea && (
<EditGiteaProvider giteaId={gitProvider.gitea?.giteaId} /> <EditGiteaProvider
giteaId={gitProvider.gitea?.giteaId}
/>
)} )}
<DialogAction <DialogAction
@@ -234,11 +242,15 @@ import {
gitProviderId: gitProvider.gitProviderId, gitProviderId: gitProvider.gitProviderId,
}) })
.then(() => { .then(() => {
toast.success("Git Provider deleted successfully"); toast.success(
"Git Provider deleted successfully",
);
refetch(); refetch();
}) })
.catch(() => { .catch(() => {
toast.error("Error deleting Git Provider"); toast.error(
"Error deleting Git Provider",
);
}); });
}} }}
> >
@@ -270,4 +282,4 @@ import {
</Card> </Card>
</div> </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

@@ -253,17 +253,17 @@ export const GiteaIcon = ({ className }: Props) => {
<g> <g>
<path <path
id="teabag" id="teabag"
style={{ fill: '#FFFFFF' }} 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" 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> <g>
<path <path
style={{ fill: '#609926' }} 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" 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 <path
style={{ fill: '#609926' }} 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" 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>

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

@@ -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({
error: 'Incomplete OAuth configuration',
missingClientId: !gitea.clientId,
missingRedirectUri: !gitea.redirectUri
});
}
// Use the state parameter to pass the giteaId
// This is more secure than adding it to the redirect URI
const state = giteaId;
const authorizationUrl = new URL(`${gitea.giteaUrl}/login/oauth/authorize`); const authorizationUrl = new URL(`${gitea.giteaUrl}/login/oauth/authorize`);
authorizationUrl.searchParams.append('client_id', gitea.clientId); authorizationUrl.searchParams.append("client_id", gitea.clientId as string);
authorizationUrl.searchParams.append('response_type', 'code'); authorizationUrl.searchParams.append("response_type", "code");
authorizationUrl.searchParams.append('redirect_uri', gitea.redirectUri); authorizationUrl.searchParams.append(
authorizationUrl.searchParams.append('scope', 'read:user repo'); "redirect_uri",
authorizationUrl.searchParams.append('state', state); gitea.redirectUri as string,
);
authorizationUrl.searchParams.append("scope", "read:user repo");
authorizationUrl.searchParams.append("state", giteaId as string);
// Redirect the user to the Gitea authorization page // Redirect user to Gitea authorization URL
res.redirect(307, authorizationUrl.toString()); return res.redirect(307, authorizationUrl.toString());
} catch (error) { } catch (error) {
console.error('Error initiating Gitea OAuth flow:', error); console.error("Error initiating Gitea OAuth flow:", error);
return res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({ error: "Internal server error" });
} }
} }

View File

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

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,22 +1,22 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { import {
apiCreateGitea, apiCreateGitea,
apiFindGiteaBranches,
apiFindOneGitea, apiFindOneGitea,
apiGiteaTestConnection, apiGiteaTestConnection,
apiUpdateGitea, apiUpdateGitea,
apiFindGiteaBranches,
} from "@/server/db/schema"; } from "@/server/db/schema";
import { db } from "@/server/db"; import { db } from "@/server/db";
import { import {
createGitea, createGitea,
findGiteaById, findGiteaById,
getGiteaBranches,
getGiteaRepositories,
haveGiteaRequirements, haveGiteaRequirements,
testGiteaConnection, testGiteaConnection,
updateGitProvider, updateGitProvider,
updateGitea, updateGitea,
getGiteaBranches,
getGiteaRepositories,
} from "@dokploy/server"; } from "@dokploy/server";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
@@ -66,7 +66,8 @@ export const giteaRouter = createTRPCRouter({
// 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 // Filter providers that meet the requirements
@@ -110,10 +111,10 @@ export const giteaRouter = createTRPCRouter({
try { try {
// Call the service layer function to get repositories // Call the service layer function to get repositories
console.log('Calling getGiteaRepositories with giteaId:', giteaId); console.log("Calling getGiteaRepositories with giteaId:", giteaId);
return await getGiteaRepositories(giteaId); return await getGiteaRepositories(giteaId);
} catch (error) { } catch (error) {
console.error('Error fetching Gitea repositories:', error); console.error("Error fetching Gitea repositories:", error);
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: error instanceof Error ? error.message : String(error), message: error instanceof Error ? error.message : String(error),
@@ -130,7 +131,8 @@ export const giteaRouter = createTRPCRouter({
if (!giteaId || !owner || !repositoryName) { if (!giteaId || !owner || !repositoryName) {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: "Gitea provider ID, owner, and repository name are required.", message:
"Gitea provider ID, owner, and repository name are required.",
}); });
} }
@@ -147,20 +149,20 @@ export const giteaRouter = createTRPCRouter({
try { try {
// Call the service layer function with the required parameters // Call the service layer function with the required parameters
console.log('Calling getGiteaBranches with:', { console.log("Calling getGiteaBranches with:", {
giteaId, giteaId,
owner, owner,
repo: repositoryName repo: repositoryName,
}); });
return await getGiteaBranches({ return await getGiteaBranches({
giteaId, giteaId,
owner, owner,
repo: repositoryName, repo: repositoryName,
id: 0 // Provide a default value for the optional id id: 0, // Provide a default value for the optional id
}); });
} catch (error) { } catch (error) {
console.error('Error fetching Gitea branches:', error); console.error("Error fetching Gitea branches:", error);
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: error instanceof Error ? error.message : String(error), message: error instanceof Error ? error.message : String(error),
@@ -173,7 +175,7 @@ export const giteaRouter = createTRPCRouter({
.input(apiGiteaTestConnection) .input(apiGiteaTestConnection)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
// Ensure giteaId is always a non-empty string // Ensure giteaId is always a non-empty string
const giteaId = input.giteaId ?? ''; const giteaId = input.giteaId ?? "";
try { try {
const giteaProvider = await findGiteaById(giteaId); const giteaProvider = await findGiteaById(giteaId);
@@ -193,7 +195,7 @@ export const giteaRouter = createTRPCRouter({
return `Found ${result} repositories`; return `Found ${result} repositories`;
} catch (error) { } catch (error) {
console.error('Gitea connection test error:', error); console.error("Gitea connection test error:", error);
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: error instanceof Error ? error.message : String(error), message: error instanceof Error ? error.message : String(error),
@@ -216,7 +218,7 @@ export const giteaRouter = createTRPCRouter({
}); });
} }
console.log('Updating Gitea provider:', input); console.log("Updating Gitea provider:", input);
if (input.name) { if (input.name) {
await updateGitProvider(input.gitProviderId, { await updateGitProvider(input.gitProviderId, {

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

@@ -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",

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",

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

@@ -22,7 +22,7 @@ export const gitea = pgTable("gitea", {
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"),
}); });

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,
@@ -362,7 +362,7 @@ export const deployRemoteCompose = async ({
command += await getGiteaCloneCommand( command += await getGiteaCloneCommand(
compose, compose,
deployment.logPath, deployment.logPath,
true true,
); );
} }

View File

@@ -45,7 +45,6 @@ export const createGitea = async (
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: {
@@ -54,7 +53,7 @@ export const findGiteaById = async (giteaId: string) => {
}); });
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",
@@ -63,22 +62,19 @@ export const findGiteaById = async (giteaId: string) => {
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>,
) => {
console.log('Updating Gitea Provider:', {
giteaId, giteaId,
updateData: { updateData: {
accessTokenPresent: !!input.accessToken, accessTokenPresent: !!input.accessToken,
refreshTokenPresent: !!input.refreshToken, refreshTokenPresent: !!input.refreshToken,
expiresAt: input.expiresAt, expiresAt: input.expiresAt,
} },
}); });
try { try {
@@ -92,13 +88,13 @@ export const updateGitea = async (
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,8 +112,10 @@ 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,
@@ -210,7 +212,8 @@ export const mechanizeDockerContainer = async (
}); });
console.log(`Service updated successfully: ${appName}`); console.log(`Service updated successfully: ${appName}`);
} catch (error: unknown) { } catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error'; const errorMessage =
error instanceof Error ? error.message : "Unknown error";
console.log(`Service not found or error: ${errorMessage}`); console.log(`Service not found or error: ${errorMessage}`);
console.log(`Creating new service: ${appName}`); console.log(`Creating new service: ${appName}`);
@@ -218,9 +221,8 @@ export const mechanizeDockerContainer = async (
await docker.createService(settings); await docker.createService(settings);
console.log(`Service created successfully: ${appName}`); console.log(`Service created successfully: ${appName}`);
} catch (createError: unknown) { } catch (createError: unknown) {
const createErrorMessage = createError instanceof Error const createErrorMessage =
? createError.message createError instanceof Error ? createError.message : "Unknown error";
: 'Unknown error';
console.error(`Failed to create service: ${createErrorMessage}`); console.error(`Failed to create service: ${createErrorMessage}`);
throw createError; throw createError;
} }

View File

@@ -90,9 +90,10 @@ 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 instanceof Error
? rmError.message ? rmError.message
: 'Unknown container cleanup error'; : "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,

View File

@@ -1,6 +1,6 @@
import { createWriteStream } from "node:fs"; import { createWriteStream } from "node:fs";
import fs from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import fs from "fs/promises";
import { paths } from "@dokploy/server/constants"; import { paths } from "@dokploy/server/constants";
import { findGiteaById, updateGitea } from "@dokploy/server/services/gitea"; import { findGiteaById, updateGitea } from "@dokploy/server/services/gitea";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
@@ -13,14 +13,16 @@ import { spawnAsync } from "../process/spawnAsync";
*/ */
export const fetchGiteaBranches = async ( export const fetchGiteaBranches = async (
giteaId: string, giteaId: string,
repoFullName: string repoFullName: string,
) => { ) => {
// Ensure owner and repo are non-empty strings // Ensure owner and repo are non-empty strings
const parts = repoFullName.split('/'); const parts = repoFullName.split("/");
// Validate that we have exactly two parts // Validate that we have exactly two parts
if (parts.length !== 2 || !parts[0] || !parts[1]) { if (parts.length !== 2 || !parts[0] || !parts[1]) {
throw new Error(`Invalid repository name format: ${repoFullName}. Expected format: owner/repo`); throw new Error(
`Invalid repository name format: ${repoFullName}. Expected format: owner/repo`,
);
} }
const [owner, repo] = parts; const [owner, repo] = parts;
@@ -30,7 +32,7 @@ export const fetchGiteaBranches = async (
giteaId, giteaId,
owner, owner,
repo, repo,
id: 0 // Provide a default value for optional id id: 0, // Provide a default value for optional id
}); });
}; };
@@ -41,10 +43,11 @@ export const getErrorCloneRequirements = (entity: {
giteaRepository?: string | null; giteaRepository?: string | null;
giteaOwner?: string | null; giteaOwner?: string | null;
giteaBranch?: string | null; giteaBranch?: string | null;
giteaPathNamespace?: string | null giteaPathNamespace?: string | null;
}) => { }) => {
const reasons: string[] = []; const reasons: string[] = [];
const { giteaBranch, giteaOwner, giteaRepository, giteaPathNamespace } = entity; const { giteaBranch, giteaOwner, giteaRepository, giteaPathNamespace } =
entity;
if (!giteaRepository) reasons.push("1. Repository not assigned."); if (!giteaRepository) reasons.push("1. Repository not assigned.");
if (!giteaOwner) reasons.push("2. Owner not specified."); if (!giteaOwner) reasons.push("2. Owner not specified.");
@@ -59,46 +62,50 @@ export const getErrorCloneRequirements = (entity: {
*/ */
export const refreshGiteaToken = async (giteaProviderId: string) => { export const refreshGiteaToken = async (giteaProviderId: string) => {
try { try {
console.log('Attempting to refresh Gitea token:', { console.log("Attempting to refresh Gitea token:", {
giteaProviderId, giteaProviderId,
timestamp: new Date().toISOString() timestamp: new Date().toISOString(),
}); });
const giteaProvider = await findGiteaById(giteaProviderId); const giteaProvider = await findGiteaById(giteaProviderId);
if (!giteaProvider?.clientId || !giteaProvider?.clientSecret || !giteaProvider?.refreshToken) { if (
console.warn('Missing credentials for token refresh'); !giteaProvider?.clientId ||
!giteaProvider?.clientSecret ||
!giteaProvider?.refreshToken
) {
console.warn("Missing credentials for token refresh");
return giteaProvider?.accessToken || null; return giteaProvider?.accessToken || null;
} }
const tokenEndpoint = `${giteaProvider.giteaUrl}/login/oauth/access_token`; const tokenEndpoint = `${giteaProvider.giteaUrl}/login/oauth/access_token`;
const params = new URLSearchParams({ const params = new URLSearchParams({
grant_type: 'refresh_token', grant_type: "refresh_token",
refresh_token: giteaProvider.refreshToken, refresh_token: giteaProvider.refreshToken,
client_id: giteaProvider.clientId, client_id: giteaProvider.clientId,
client_secret: giteaProvider.clientSecret, client_secret: giteaProvider.clientSecret,
}); });
console.log('Token Endpoint:', tokenEndpoint); console.log("Token Endpoint:", tokenEndpoint);
console.log('Request Parameters:', params.toString()); console.log("Request Parameters:", params.toString());
const response = await fetch(tokenEndpoint, { const response = await fetch(tokenEndpoint, {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', "Content-Type": "application/x-www-form-urlencoded",
'Accept': 'application/json' Accept: "application/json",
}, },
body: params.toString(), body: params.toString(),
}); });
console.log('Token Refresh Response:', { console.log("Token Refresh Response:", {
status: response.status, status: response.status,
statusText: response.statusText statusText: response.statusText,
}); });
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
console.error('Token Refresh Failed:', errorText); console.error("Token Refresh Failed:", errorText);
return giteaProvider?.accessToken || null; return giteaProvider?.accessToken || null;
} }
@@ -106,11 +113,11 @@ export const refreshGiteaToken = async (giteaProviderId: string) => {
const { access_token, refresh_token, expires_in } = data; const { access_token, refresh_token, expires_in } = data;
if (!access_token) { if (!access_token) {
console.error('Missing access token in refresh response'); console.error("Missing access token in refresh response");
return giteaProvider?.accessToken || null; return giteaProvider?.accessToken || null;
} }
const expiresAt = Date.now() + ((expires_in || 3600) * 1000); const expiresAt = Date.now() + (expires_in || 3600) * 1000;
const expiresAtSeconds = Math.floor(expiresAt / 1000); const expiresAtSeconds = Math.floor(expiresAt / 1000);
await updateGitea(giteaProviderId, { await updateGitea(giteaProviderId, {
@@ -119,10 +126,10 @@ export const refreshGiteaToken = async (giteaProviderId: string) => {
expiresAt: expiresAtSeconds, expiresAt: expiresAtSeconds,
}); });
console.log('Gitea token refreshed successfully.'); console.log("Gitea token refreshed successfully.");
return access_token; return access_token;
} catch (error) { } catch (error) {
console.error('Token Refresh Error:', error); console.error("Token Refresh Error:", error);
// Return the existing token if refresh fails // Return the existing token if refresh fails
const giteaProvider = await findGiteaById(giteaProviderId); const giteaProvider = await findGiteaById(giteaProviderId);
return giteaProvider?.accessToken || null; return giteaProvider?.accessToken || null;
@@ -132,8 +139,19 @@ export const refreshGiteaToken = async (giteaProviderId: string) => {
/** /**
* Generate a secure Git clone command with proper validation * Generate a secure Git clone command with proper validation
*/ */
export const getGiteaCloneCommand = async (entity: any, logPath: string, isCompose = false) => { export const getGiteaCloneCommand = async (
const { appName, giteaBranch, giteaId, giteaOwner, giteaRepository, serverId } = entity; entity: any,
logPath: string,
isCompose = false,
) => {
const {
appName,
giteaBranch,
giteaId,
giteaOwner,
giteaRepository,
serverId,
} = entity;
if (!serverId) { if (!serverId) {
throw new TRPCError({ throw new TRPCError({
@@ -155,7 +173,7 @@ export const getGiteaCloneCommand = async (entity: any, logPath: string, isCompo
const outputPath = join(basePath, appName, "code"); const outputPath = join(basePath, appName, "code");
const giteaProvider = await findGiteaById(giteaId); const giteaProvider = await findGiteaById(giteaId);
const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, ''); const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, "");
const repoClone = `${giteaOwner}/${giteaRepository}.git`; const repoClone = `${giteaOwner}/${giteaRepository}.git`;
const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`; const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`;
@@ -194,13 +212,15 @@ export const getGiteaCloneCommand = async (entity: any, logPath: string, isCompo
export const cloneGiteaRepository = async ( export const cloneGiteaRepository = async (
entity: any, entity: any,
logPath?: string, logPath?: string,
isCompose: boolean = false isCompose = false,
) => { ) => {
// If logPath is not provided, generate a default log path // If logPath is not provided, generate a default log path
const actualLogPath = logPath || join( const actualLogPath =
paths()[isCompose ? 'COMPOSE_PATH' : 'APPLICATIONS_PATH'], logPath ||
join(
paths()[isCompose ? "COMPOSE_PATH" : "APPLICATIONS_PATH"],
entity.appName, entity.appName,
'clone.log' "clone.log",
); );
const writeStream = createWriteStream(actualLogPath, { flags: "a" }); const writeStream = createWriteStream(actualLogPath, { flags: "a" });
@@ -231,7 +251,7 @@ export const cloneGiteaRepository = async (
const outputPath = join(basePath, appName, "code"); const outputPath = join(basePath, appName, "code");
// Log path information // Log path information
writeStream.write(`\nPath Information:\n`); writeStream.write("\nPath Information:\n");
writeStream.write(`Base Path: ${basePath}\n`); writeStream.write(`Base Path: ${basePath}\n`);
writeStream.write(`Output Path: ${outputPath}\n`); writeStream.write(`Output Path: ${outputPath}\n`);
@@ -241,19 +261,24 @@ export const cloneGiteaRepository = async (
// Additional step - verify directory exists and is empty // Additional step - verify directory exists and is empty
try { try {
const filesCheck = await fs.readdir(outputPath); const filesCheck = await fs.readdir(outputPath);
writeStream.write(`Directory after cleanup - files: ${filesCheck.length}\n`); writeStream.write(
`Directory after cleanup - files: ${filesCheck.length}\n`,
);
if (filesCheck.length > 0) { if (filesCheck.length > 0) {
writeStream.write(`WARNING: Directory not empty after cleanup!\n`); writeStream.write("WARNING: Directory not empty after cleanup!\n");
// Force remove with shell command if recreateDirectory didn't work // Force remove with shell command if recreateDirectory didn't work
if (entity.serverId) { if (entity.serverId) {
writeStream.write(`Attempting forceful cleanup via shell command\n`); writeStream.write("Attempting forceful cleanup via shell command\n");
await execAsyncRemote(entity.serverId, `rm -rf "${outputPath}" && mkdir -p "${outputPath}"`, await execAsyncRemote(
data => writeStream.write(`Cleanup output: ${data}\n`)); entity.serverId,
`rm -rf "${outputPath}" && mkdir -p "${outputPath}"`,
(data) => writeStream.write(`Cleanup output: ${data}\n`),
);
} else { } else {
// Fallback to direct fs operations if serverId not available // Fallback to direct fs operations if serverId not available
writeStream.write(`Attempting direct fs removal\n`); writeStream.write("Attempting direct fs removal\n");
await fs.rm(outputPath, { recursive: true, force: true }); await fs.rm(outputPath, { recursive: true, force: true });
await fs.mkdir(outputPath, { recursive: true }); await fs.mkdir(outputPath, { recursive: true });
} }
@@ -264,11 +289,13 @@ export const cloneGiteaRepository = async (
} }
const repoClone = `${giteaOwner}/${giteaRepository}.git`; const repoClone = `${giteaOwner}/${giteaRepository}.git`;
const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, ''); const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, "");
const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`; const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`;
writeStream.write(`\nCloning Repo ${repoClone} to ${outputPath}...\n`); writeStream.write(`\nCloning Repo ${repoClone} to ${outputPath}...\n`);
writeStream.write(`Clone URL (masked): https://oauth2:***@${baseUrl}/${repoClone}\n`); writeStream.write(
`Clone URL (masked): https://oauth2:***@${baseUrl}/${repoClone}\n`,
);
// First try standard git clone // First try standard git clone
try { try {
@@ -283,38 +310,43 @@ export const cloneGiteaRepository = async (
"--recurse-submodules", "--recurse-submodules",
cloneUrl, cloneUrl,
outputPath, outputPath,
"--progress" "--progress",
], ],
(data) => { (data) => {
if (writeStream.writable) { if (writeStream.writable) {
writeStream.write(data); writeStream.write(data);
} }
} },
); );
writeStream.write(`\nStandard git clone succeeded\n`); writeStream.write("\nStandard git clone succeeded\n");
} catch (cloneError) { } catch (cloneError) {
writeStream.write(`\nStandard git clone failed: ${cloneError}\n`); writeStream.write(`\nStandard git clone failed: ${cloneError}\n`);
writeStream.write(`Falling back to git init + fetch approach...\n`); writeStream.write("Falling back to git init + fetch approach...\n");
// Retry cleanup one more time // Retry cleanup one more time
if (entity.serverId) { if (entity.serverId) {
await execAsyncRemote(entity.serverId, `rm -rf "${outputPath}" && mkdir -p "${outputPath}"`, await execAsyncRemote(
data => writeStream.write(`Cleanup retry: ${data}\n`)); entity.serverId,
`rm -rf "${outputPath}" && mkdir -p "${outputPath}"`,
(data) => writeStream.write(`Cleanup retry: ${data}\n`),
);
} else { } else {
await fs.rm(outputPath, { recursive: true, force: true }); await fs.rm(outputPath, { recursive: true, force: true });
await fs.mkdir(outputPath, { recursive: true }); await fs.mkdir(outputPath, { recursive: true });
} }
// Initialize git repo // Initialize git repo
writeStream.write(`Initializing git repository...\n`); writeStream.write("Initializing git repository...\n");
await spawnAsync("git", ["init", outputPath], data => writeStream.write(data)); await spawnAsync("git", ["init", outputPath], (data) =>
writeStream.write(data),
);
// Set remote origin // Set remote origin
writeStream.write(`Setting remote origin...\n`); writeStream.write("Setting remote origin...\n");
await spawnAsync( await spawnAsync(
"git", "git",
["-C", outputPath, "remote", "add", "origin", cloneUrl], ["-C", outputPath, "remote", "add", "origin", cloneUrl],
data => writeStream.write(data) (data) => writeStream.write(data),
); );
// Fetch branch // Fetch branch
@@ -322,7 +354,7 @@ export const cloneGiteaRepository = async (
await spawnAsync( await spawnAsync(
"git", "git",
["-C", outputPath, "fetch", "--depth", "1", "origin", giteaBranch], ["-C", outputPath, "fetch", "--depth", "1", "origin", giteaBranch],
data => writeStream.write(data) (data) => writeStream.write(data),
); );
// Checkout branch // Checkout branch
@@ -330,18 +362,21 @@ export const cloneGiteaRepository = async (
await spawnAsync( await spawnAsync(
"git", "git",
["-C", outputPath, "checkout", "FETCH_HEAD"], ["-C", outputPath, "checkout", "FETCH_HEAD"],
data => writeStream.write(data) (data) => writeStream.write(data),
); );
writeStream.write(`Git init and fetch completed successfully\n`); writeStream.write("Git init and fetch completed successfully\n");
} }
// Verify clone // Verify clone
const files = await fs.readdir(outputPath); const files = await fs.readdir(outputPath);
writeStream.write(`\nClone Verification:\n`); writeStream.write("\nClone Verification:\n");
writeStream.write(`Files found: ${files.length}\n`); writeStream.write(`Files found: ${files.length}\n`);
if (files.length > 0) { if (files.length > 0) {
files.slice(0, 10).forEach(file => writeStream.write(`- ${file}\n`)); // Using a for loop instead of forEach
for (let i = 0; i < Math.min(files.length, 10); i++) {
writeStream.write(`- ${files[i]}\n`);
}
} }
if (files.length === 0) { if (files.length === 0) {
@@ -357,7 +392,6 @@ export const cloneGiteaRepository = async (
} }
}; };
/** /**
* Clone a Gitea repository locally for a Compose configuration * Clone a Gitea repository locally for a Compose configuration
* Leverages the existing comprehensive cloneGiteaRepository function * Leverages the existing comprehensive cloneGiteaRepository function
@@ -366,8 +400,8 @@ export const cloneRawGiteaRepository = async (entity: any) => {
// Merge the existing entity with compose-specific properties // Merge the existing entity with compose-specific properties
const composeEntity = { const composeEntity = {
...entity, ...entity,
sourceType: 'compose', sourceType: "compose",
isCompose: true isCompose: true,
}; };
// Call cloneGiteaRepository with the modified entity // Call cloneGiteaRepository with the modified entity
@@ -380,13 +414,17 @@ export const cloneRawGiteaRepository = async (entity: any) => {
*/ */
export const cloneRawGiteaRepositoryRemote = async (compose: any) => { export const cloneRawGiteaRepositoryRemote = async (compose: any) => {
const { COMPOSE_PATH } = paths(true); const { COMPOSE_PATH } = paths(true);
const logPath = join(COMPOSE_PATH, compose.appName, 'clone.log'); const logPath = join(COMPOSE_PATH, compose.appName, "clone.log");
// Reuse the existing getGiteaCloneCommand function // Reuse the existing getGiteaCloneCommand function
const command = await getGiteaCloneCommand({ const command = await getGiteaCloneCommand(
{
...compose, ...compose,
isCompose: true isCompose: true,
}, logPath, true); },
logPath,
true,
);
if (!compose.serverId) { if (!compose.serverId) {
throw new TRPCError({ throw new TRPCError({
@@ -424,7 +462,7 @@ export const testGiteaConnection = async (input: { giteaId: string }) => {
}); });
} }
console.log('Gitea Provider Found:', { console.log("Gitea Provider Found:", {
id: giteaProvider.giteaId, id: giteaProvider.giteaId,
url: giteaProvider.giteaUrl, url: giteaProvider.giteaUrl,
hasAccessToken: !!giteaProvider.accessToken, hasAccessToken: !!giteaProvider.accessToken,
@@ -443,38 +481,42 @@ export const testGiteaConnection = async (input: { giteaId: string }) => {
} }
// Make API request to test connection // Make API request to test connection
console.log('Making API request to test connection...'); console.log("Making API request to test connection...");
// Construct proper URL for the API request // Construct proper URL for the API request
const baseUrl = provider.giteaUrl.replace(/\/+$/, ''); // Remove trailing slashes const baseUrl = provider.giteaUrl.replace(/\/+$/, ""); // Remove trailing slashes
const url = `${baseUrl}/api/v1/user/repos`; const url = `${baseUrl}/api/v1/user/repos`;
console.log(`Testing connection to: ${url}`); console.log(`Testing connection to: ${url}`);
const response = await fetch(url, { const response = await fetch(url, {
headers: { headers: {
'Accept': 'application/json', Accept: "application/json",
'Authorization': `token ${provider.accessToken}` Authorization: `token ${provider.accessToken}`,
} },
}); });
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
console.error('Repository API failed:', errorText); console.error("Repository API failed:", errorText);
throw new Error(`Failed to connect to Gitea API: ${response.status} ${response.statusText}`); throw new Error(
`Failed to connect to Gitea API: ${response.status} ${response.statusText}`,
);
} }
const repos = await response.json(); const repos = await response.json();
console.log(`Successfully connected to Gitea API. Found ${repos.length} repositories.`); console.log(
`Successfully connected to Gitea API. Found ${repos.length} repositories.`,
);
// Update lastAuthenticatedAt // Update lastAuthenticatedAt
await updateGitea(giteaId, { await updateGitea(giteaId, {
lastAuthenticatedAt: Math.floor(Date.now() / 1000) lastAuthenticatedAt: Math.floor(Date.now() / 1000),
}); });
return repos.length; return repos.length;
} catch (error) { } catch (error) {
console.error('Gitea Connection Test Error:', error); console.error("Gitea Connection Test Error:", error);
throw error; throw error;
} }
}; };
@@ -494,14 +536,14 @@ export const getGiteaRepositories = async (giteaId?: string) => {
const giteaProvider = await findGiteaById(giteaId); const giteaProvider = await findGiteaById(giteaId);
// Construct the URL for fetching repositories // Construct the URL for fetching repositories
const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, ''); const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, "");
const url = `${baseUrl}/api/v1/user/repos`; const url = `${baseUrl}/api/v1/user/repos`;
const response = await fetch(url, { const response = await fetch(url, {
headers: { headers: {
'Accept': 'application/json', Accept: "application/json",
'Authorization': `token ${giteaProvider.accessToken}` Authorization: `token ${giteaProvider.accessToken}`,
} },
}); });
if (!response.ok) { if (!response.ok) {
@@ -519,8 +561,8 @@ export const getGiteaRepositories = async (giteaId?: string) => {
name: repo.name, name: repo.name,
url: repo.full_name, url: repo.full_name,
owner: { owner: {
username: repo.owner.login username: repo.owner.login,
} },
})); }));
return mappedRepositories; return mappedRepositories;
@@ -543,14 +585,14 @@ export const getGiteaBranches = async (input: {
const giteaProvider = await findGiteaById(input.giteaId); const giteaProvider = await findGiteaById(input.giteaId);
// Construct the URL for fetching branches // Construct the URL for fetching branches
const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, ''); const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, "");
const url = `${baseUrl}/api/v1/repos/${input.owner}/${input.repo}/branches`; const url = `${baseUrl}/api/v1/repos/${input.owner}/${input.repo}/branches`;
const response = await fetch(url, { const response = await fetch(url, {
headers: { headers: {
'Accept': 'application/json', Accept: "application/json",
'Authorization': `token ${giteaProvider.accessToken}` Authorization: `token ${giteaProvider.accessToken}`,
} },
}); });
if (!response.ok) { if (!response.ok) {
@@ -564,8 +606,8 @@ export const getGiteaBranches = async (input: {
id: branch.name, id: branch.name,
name: branch.name, name: branch.name,
commit: { commit: {
id: branch.commit.id id: branch.commit.id,
} },
})); }));
}; };
@@ -578,5 +620,5 @@ export default {
testGiteaConnection, testGiteaConnection,
getGiteaRepositories, getGiteaRepositories,
getGiteaBranches, getGiteaBranches,
fetchGiteaBranches fetchGiteaBranches,
}; };

4
pnpm-lock.yaml generated
View File

@@ -242,7 +242,7 @@ importers:
specifier: ^10.43.6 specifier: ^10.43.6
version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@trpc/server': '@trpc/server':
specifier: ^10.43.6 specifier: ^10.45.2
version: 10.45.2 version: 10.45.2
'@uiw/codemirror-theme-github': '@uiw/codemirror-theme-github':
specifier: ^4.22.1 specifier: ^4.22.1
@@ -471,7 +471,7 @@ importers:
specifier: 4.0.9 specifier: 4.0.9
version: 4.0.9 version: 4.0.9
'@types/node': '@types/node':
specifier: ^18.17.0 specifier: ^18.19.42
version: 18.19.42 version: 18.19.42
'@types/node-os-utils': '@types/node-os-utils':
specifier: 1.3.4 specifier: 1.3.4