Merge branch 'feat/add-gitea-repo' of https://github.com/jrparks/dokploy into feat/add-gitea-repo

This commit is contained in:
Jason Parks
2025-03-19 11:32:29 -06:00
58 changed files with 6813 additions and 1793 deletions

View File

@@ -27,6 +27,7 @@ if (typeof window === "undefined") {
const baseApp: ApplicationNested = {
applicationId: "",
herokuVersion: "",
cleanCache: false,
watchPaths: [],
applicationStatus: "done",
appName: "",

View File

@@ -7,6 +7,7 @@ import { expect, test } from "vitest";
const baseApp: ApplicationNested = {
applicationId: "",
herokuVersion: "",
cleanCache: false,
applicationStatus: "done",
appName: "",
autoDeploy: true,

View File

@@ -40,7 +40,7 @@ interface Props {
}
const AddRedirectchema = z.object({
replicas: z.number(),
replicas: z.number().min(1, "Replicas must be at least 1"),
registryId: z.string(),
});
@@ -130,9 +130,11 @@ export const ShowClusterSettings = ({ applicationId }: Props) => {
placeholder="1"
{...field}
onChange={(e) => {
field.onChange(Number(e.target.value));
const value = e.target.value;
field.onChange(value === "" ? 0 : Number(value));
}}
type="number"
value={field.value || ""}
/>
</FormControl>

View File

@@ -41,6 +41,7 @@ import { toast } from "sonner";
import { domain } from "@/server/db/validations/domain";
import { zodResolver } from "@hookform/resolvers/zod";
import { Dices } from "lucide-react";
import Link from "next/link";
import type z from "zod";
type Domain = z.infer<typeof domain>;
@@ -83,6 +84,13 @@ export const AddDomain = ({
const { mutateAsync: generateDomain, isLoading: isLoadingGenerate } =
api.domain.generateDomain.useMutation();
const { data: canGenerateTraefikMeDomains } =
api.domain.canGenerateTraefikMeDomains.useQuery({
serverId: application?.serverId || "",
});
console.log("canGenerateTraefikMeDomains", canGenerateTraefikMeDomains);
const form = useForm<Domain>({
resolver: zodResolver(domain),
defaultValues: {
@@ -186,6 +194,21 @@ export const AddDomain = ({
name="host"
render={({ field }) => (
<FormItem>
{!canGenerateTraefikMeDomains &&
field.value.includes("traefik.me") && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{application?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to make your traefik.me domain work.
</AlertBlock>
)}
<FormLabel>Host</FormLabel>
<div className="flex gap-2">
<FormControl>

View File

@@ -16,8 +16,8 @@ import {
Ban,
CheckCircle2,
Hammer,
HelpCircle,
RefreshCcw,
Rocket,
Terminal,
} from "lucide-react";
import { useRouter } from "next/router";
@@ -55,7 +55,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
<CardTitle className="text-xl">Deploy Settings</CardTitle>
</CardHeader>
<CardContent className="flex flex-row gap-4 flex-wrap">
<TooltipProvider delayDuration={0}>
<TooltipProvider delayDuration={0} disableHoverableContent={false}>
<DialogAction
title="Deploy Application"
description="Are you sure you want to deploy this application?"
@@ -79,12 +79,14 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
<Button
variant="default"
isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
>
Deploy
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Rocket className="size-4 mr-1" />
Deploy
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -114,9 +116,24 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
});
}}
>
<Button variant="secondary" isLoading={isReloading}>
Reload
<RefreshCcw className="size-4" />
<Button
variant="secondary"
isLoading={isReloading}
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center">
<RefreshCcw className="size-4 mr-1" />
Reload
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Reload the application without rebuilding it</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DialogAction>
<DialogAction
@@ -139,13 +156,14 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
<Button
variant="secondary"
isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
>
Rebuild
<Hammer className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Hammer className="size-4 mr-1" />
Rebuild
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -180,13 +198,14 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
<Button
variant="secondary"
isLoading={isStarting}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
>
Start
<CheckCircle2 className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<CheckCircle2 className="size-4 mr-1" />
Start
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -219,13 +238,14 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
<Button
variant="destructive"
isLoading={isStopping}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
>
Stop
<Ban className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Ban className="size-4 mr-1" />
Stop
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -241,15 +261,18 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
<Button variant="outline">
<Terminal />
<Button
variant="outline"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Terminal className="size-4 mr-1" />
Open Terminal
</Button>
</DockerTerminalModal>
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
<span className="text-sm font-medium">Autodeploy</span>
<Switch
aria-label="Toggle italic"
aria-label="Toggle autodeploy"
checked={data?.autoDeploy || false}
onCheckedChange={async (enabled) => {
await update({
@@ -264,14 +287,14 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
toast.error("Error updating Auto Deploy");
});
}}
className="flex flex-row gap-2 items-center"
className="flex flex-row gap-2 items-center data-[state=checked]:bg-primary"
/>
</div>
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
<span className="text-sm font-medium">Clean Cache</span>
<Switch
aria-label="Toggle italic"
aria-label="Toggle clean cache"
checked={data?.cleanCache || false}
onCheckedChange={async (enabled) => {
await update({
@@ -286,7 +309,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
toast.error("Error updating Clean Cache");
});
}}
className="flex flex-row gap-2 items-center"
className="flex flex-row gap-2 items-center data-[state=checked]:bg-primary"
/>
</div>
</CardContent>

View File

@@ -41,6 +41,7 @@ import {
import { domainCompose } from "@/server/db/validations/domain";
import { zodResolver } from "@hookform/resolvers/zod";
import { DatabaseZap, Dices, RefreshCw } from "lucide-react";
import Link from "next/link";
import type z from "zod";
type Domain = z.infer<typeof domainCompose>;
@@ -102,6 +103,11 @@ export const AddDomainCompose = ({
? api.domain.update.useMutation()
: api.domain.create.useMutation();
const { data: canGenerateTraefikMeDomains } =
api.domain.canGenerateTraefikMeDomains.useQuery({
serverId: compose?.serverId || "",
});
const form = useForm<Domain>({
resolver: zodResolver(domainCompose),
defaultValues: {
@@ -313,6 +319,21 @@ export const AddDomainCompose = ({
name="host"
render={({ field }) => (
<FormItem>
{!canGenerateTraefikMeDomains &&
field.value.includes("traefik.me") && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{compose?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}
</Link>{" "}
to make your traefik.me domain work.
</AlertBlock>
)}
<FormLabel>Host</FormLabel>
<div className="flex gap-2">
<FormControl>

View File

@@ -8,11 +8,11 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
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, RefreshCcw, Rocket, Terminal } from "lucide-react";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
interface Props {
composeId: string;
@@ -34,7 +34,7 @@ export const ComposeActions = ({ composeId }: Props) => {
api.compose.stop.useMutation();
return (
<div className="flex flex-row gap-4 w-full flex-wrap ">
<TooltipProvider delayDuration={0}>
<TooltipProvider delayDuration={0} disableHoverableContent={false}>
<DialogAction
title="Deploy Compose"
description="Are you sure you want to deploy this compose?"
@@ -58,12 +58,14 @@ export const ComposeActions = ({ composeId }: Props) => {
<Button
variant="default"
isLoading={data?.composeStatus === "running"}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
>
Deploy
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Rocket className="size-4 mr-1" />
Deploy
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -74,36 +76,37 @@ export const ComposeActions = ({ composeId }: Props) => {
</Button>
</DialogAction>
<DialogAction
title="Rebuild Compose"
description="Are you sure you want to rebuild this compose?"
title="Reload Compose"
description="Are you sure you want to reload this compose?"
type="default"
onClick={async () => {
await redeploy({
composeId: composeId,
})
.then(() => {
toast.success("Compose rebuilt successfully");
toast.success("Compose reloaded successfully");
refetch();
})
.catch(() => {
toast.error("Error rebuilding compose");
toast.error("Error reloading compose");
});
}}
>
<Button
variant="secondary"
isLoading={data?.composeStatus === "running"}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
>
Rebuild
<Hammer className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<RefreshCcw className="size-4 mr-1" />
Reload
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Only rebuilds the compose without downloading new code</p>
<p>Reload the compose without rebuilding it</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
@@ -131,13 +134,14 @@ export const ComposeActions = ({ composeId }: Props) => {
<Button
variant="secondary"
isLoading={isStarting}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
>
Start
<CheckCircle2 className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<CheckCircle2 className="size-4 mr-1" />
Start
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -169,13 +173,14 @@ export const ComposeActions = ({ composeId }: Props) => {
<Button
variant="destructive"
isLoading={isStopping}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 group focus-visible:ring-2 focus-visible:ring-offset-2"
>
Stop
<Ban className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Ban className="size-4 mr-1" />
Stop
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -191,15 +196,18 @@ export const ComposeActions = ({ composeId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
<Button variant="outline">
<Terminal />
<Button
variant="outline"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Terminal className="size-4 mr-1" />
Open Terminal
</Button>
</DockerTerminalModal>
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
<span className="text-sm font-medium">Autodeploy</span>
<Switch
aria-label="Toggle italic"
aria-label="Toggle autodeploy"
checked={data?.autoDeploy || false}
onCheckedChange={async (enabled) => {
await update({
@@ -214,7 +222,7 @@ export const ComposeActions = ({ composeId }: Props) => {
toast.error("Error updating Auto Deploy");
});
}}
className="flex flex-row gap-2 items-center"
className="flex flex-row gap-2 items-center data-[state=checked]:bg-primary"
/>
</div>
</div>

View File

@@ -0,0 +1,375 @@
import { DrawerLogs } from "@/components/shared/drawer-logs";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import copy from "copy-to-clipboard";
import { debounce } from "lodash";
import { CheckIcon, ChevronsUpDown, Copy, RotateCcw } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import type { ServiceType } from "../../application/advanced/show-resources";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
interface Props {
databaseId: string;
databaseType: Exclude<ServiceType, "application" | "redis">;
serverId: string | null;
}
const RestoreBackupSchema = z.object({
destinationId: z
.string({
required_error: "Please select a destination",
})
.min(1, {
message: "Destination is required",
}),
backupFile: z
.string({
required_error: "Please select a backup file",
})
.min(1, {
message: "Backup file is required",
}),
databaseName: z
.string({
required_error: "Please enter a database name",
})
.min(1, {
message: "Database name is required",
}),
});
type RestoreBackup = z.infer<typeof RestoreBackupSchema>;
export const RestoreBackup = ({
databaseId,
databaseType,
serverId,
}: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [search, setSearch] = useState("");
const { data: destinations = [] } = api.destination.all.useQuery();
const form = useForm<RestoreBackup>({
defaultValues: {
destinationId: "",
backupFile: "",
databaseName: "",
},
resolver: zodResolver(RestoreBackupSchema),
});
const destionationId = form.watch("destinationId");
const debouncedSetSearch = debounce((value: string) => {
setSearch(value);
}, 300);
const { data: files = [], isLoading } = api.backup.listBackupFiles.useQuery(
{
destinationId: destionationId,
search,
serverId: serverId ?? "",
},
{
enabled: isOpen && !!destionationId,
},
);
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [filteredLogs, setFilteredLogs] = useState<LogLine[]>([]);
const [isDeploying, setIsDeploying] = useState(false);
// const { mutateAsync: restore, isLoading: isRestoring } =
// api.backup.restoreBackup.useMutation();
api.backup.restoreBackupWithLogs.useSubscription(
{
databaseId,
databaseType,
databaseName: form.watch("databaseName"),
backupFile: form.watch("backupFile"),
destinationId: form.watch("destinationId"),
},
{
enabled: isDeploying,
onData(log) {
if (!isDrawerOpen) {
setIsDrawerOpen(true);
}
if (log === "Restore completed successfully!") {
setIsDeploying(false);
}
const parsedLogs = parseLogs(log);
setFilteredLogs((prev) => [...prev, ...parsedLogs]);
},
onError(error) {
console.error("Restore logs error:", error);
setIsDeploying(false);
},
},
);
const onSubmit = async (_data: RestoreBackup) => {
setIsDeploying(true);
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="outline">
<RotateCcw className="mr-2 size-4" />
Restore Backup
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle className="flex items-center">
<RotateCcw className="mr-2 size-4" />
Restore Backup
</DialogTitle>
<DialogDescription>
Select a destination and search for backup files
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form
id="hook-form-restore-backup"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="destinationId"
render={({ field }) => (
<FormItem className="">
<FormLabel>Destination</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
)}
>
{field.value
? destinations.find(
(d) => d.destinationId === field.value,
)?.name
: "Select Destination"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput
placeholder="Search destinations..."
className="h-9"
/>
<CommandEmpty>No destinations found.</CommandEmpty>
<ScrollArea className="h-64">
<CommandGroup>
{destinations.map((destination) => (
<CommandItem
value={destination.destinationId}
key={destination.destinationId}
onSelect={() => {
form.setValue(
"destinationId",
destination.destinationId,
);
}}
>
{destination.name}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
destination.destinationId === field.value
? "opacity-100"
: "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</ScrollArea>
</Command>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="backupFile"
render={({ field }) => (
<FormItem className="">
<FormLabel className="flex items-center justify-between">
Search Backup Files
{field.value && (
<Badge variant="outline">
{field.value}
<Copy
className="ml-2 size-4 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
copy(field.value);
toast.success("Backup file copied to clipboard");
}}
/>
</Badge>
)}
</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
)}
>
{field.value || "Search and select a backup file"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="p-0" align="start">
<Command>
<CommandInput
placeholder="Search backup files..."
onValueChange={debouncedSetSearch}
className="h-9"
/>
{isLoading ? (
<div className="py-6 text-center text-sm">
Loading backup files...
</div>
) : files.length === 0 && search ? (
<div className="py-6 text-center text-sm text-muted-foreground">
No backup files found for "{search}"
</div>
) : files.length === 0 ? (
<div className="py-6 text-center text-sm text-muted-foreground">
No backup files available
</div>
) : (
<ScrollArea className="h-64">
<CommandGroup>
{files.map((file) => (
<CommandItem
value={file}
key={file}
onSelect={() => {
form.setValue("backupFile", file);
}}
>
<div className="flex w-full justify-between">
<span>{file}</span>
</div>
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
file === field.value
? "opacity-100"
: "opacity-0",
)}
/>
</CommandItem>
))}
</CommandGroup>
</ScrollArea>
)}
</Command>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="databaseName"
render={({ field }) => (
<FormItem className="">
<FormLabel>Database Name</FormLabel>
<FormControl>
<Input {...field} placeholder="Enter database name" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button
isLoading={isDeploying}
form="hook-form-restore-backup"
type="submit"
disabled={!form.watch("backupFile")}
>
Restore
</Button>
</DialogFooter>
</form>
</Form>
<DrawerLogs
isOpen={isDrawerOpen}
onClose={() => {
setIsDrawerOpen(false);
setFilteredLogs([]);
setIsDeploying(false);
// refetch();
}}
filteredLogs={filteredLogs}
/>
</DialogContent>
</Dialog>
);
};

View File

@@ -20,6 +20,7 @@ import { useState } from "react";
import { toast } from "sonner";
import type { ServiceType } from "../../application/advanced/show-resources";
import { AddBackup } from "./add-backup";
import { RestoreBackup } from "./restore-backup";
import { UpdateBackup } from "./update-backup";
interface Props {
@@ -71,7 +72,14 @@ export const ShowBackups = ({ id, type }: Props) => {
</div>
{postgres && postgres?.backups?.length > 0 && (
<AddBackup databaseId={id} databaseType={type} refetch={refetch} />
<div className="flex flex-col lg:flex-row gap-4 w-full lg:w-auto">
<AddBackup databaseId={id} databaseType={type} refetch={refetch} />
<RestoreBackup
databaseId={id}
databaseType={type}
serverId={postgres.serverId}
/>
</div>
)}
</CardHeader>
<CardContent className="flex flex-col gap-4">
@@ -98,11 +106,18 @@ export const ShowBackups = ({ id, type }: Props) => {
<span className="text-base text-muted-foreground">
No backups configured
</span>
<AddBackup
databaseId={id}
databaseType={type}
refetch={refetch}
/>
<div className="flex flex-col sm:flex-row gap-4 w-full sm:w-auto">
<AddBackup
databaseId={id}
databaseType={type}
refetch={refetch}
/>
<RestoreBackup
databaseId={id}
databaseType={type}
serverId={postgres.serverId}
/>
</div>
</div>
) : (
<div className="flex flex-col pt-2">
@@ -183,6 +198,7 @@ export const ShowBackups = ({ id, type }: Props) => {
<TooltipContent>Run Manual Backup</TooltipContent>
</Tooltip>
</TooltipProvider>
<UpdateBackup
backupId={backup.backupId}
refetch={refetch}

View File

@@ -119,7 +119,6 @@ export const DockerLogsId: React.FC<Props> = ({
const wsUrl = `${protocol}//${
window.location.host
}/docker-container-logs?${params.toString()}`;
console.log("Connecting to WebSocket:", wsUrl);
const ws = new WebSocket(wsUrl);
const resetNoDataTimeout = () => {
@@ -136,7 +135,6 @@ export const DockerLogsId: React.FC<Props> = ({
ws.close();
return;
}
console.log("WebSocket connected");
resetNoDataTimeout();
};

View File

@@ -111,7 +111,10 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link href="/dashboard/settings" className="text-primary">
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}

View File

@@ -10,13 +10,7 @@ import {
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,
HelpCircle,
RefreshCcw,
Terminal,
} from "lucide-react";
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
@@ -92,12 +86,14 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
<Button
variant="default"
isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Deploy
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Rocket className="size-4 mr-1" />
Deploy
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -107,6 +103,8 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
</Tooltip>
</Button>
</DialogAction>
</TooltipProvider>
<TooltipProvider delayDuration={0}>
<DialogAction
title="Reload Mariadb"
description="Are you sure you want to reload this mariadb?"
@@ -128,13 +126,14 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
<Button
variant="secondary"
isLoading={isReloading}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Reload
<RefreshCcw className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<RefreshCcw className="size-4 mr-1" />
Reload
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -144,7 +143,9 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
</Tooltip>
</Button>
</DialogAction>
{data?.applicationStatus === "idle" ? (
</TooltipProvider>
{data?.applicationStatus === "idle" ? (
<TooltipProvider delayDuration={0}>
<DialogAction
title="Start Mariadb"
description="Are you sure you want to start this mariadb?"
@@ -165,13 +166,14 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
<Button
variant="secondary"
isLoading={isStarting}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Start
<CheckCircle2 className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<CheckCircle2 className="size-4 mr-1" />
Start
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -184,7 +186,9 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
</Tooltip>
</Button>
</DialogAction>
) : (
</TooltipProvider>
) : (
<TooltipProvider delayDuration={0}>
<DialogAction
title="Stop Mariadb"
description="Are you sure you want to stop this mariadb?"
@@ -204,13 +208,14 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
<Button
variant="destructive"
isLoading={isStopping}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Stop
<Ban className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Ban className="size-4 mr-1" />
Stop
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -220,15 +225,29 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
</Tooltip>
</Button>
</DialogAction>
)}
</TooltipProvider>
</TooltipProvider>
)}
<DockerTerminalModal
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
<Button variant="outline">
<Terminal />
Open Terminal
<Button
variant="outline"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center">
<Terminal className="size-4 mr-1" />
Open Terminal
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Open a terminal to the MariaDB container</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DockerTerminalModal>
</CardContent>

View File

@@ -111,7 +111,10 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link href="/dashboard/settings" className="text-primary">
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}

View File

@@ -10,13 +10,7 @@ import {
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,
HelpCircle,
RefreshCcw,
Terminal,
} from "lucide-react";
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
@@ -91,12 +85,14 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
<Button
variant="default"
isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Deploy
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Rocket className="size-4 mr-1" />
Deploy
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -127,13 +123,14 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
<Button
variant="secondary"
isLoading={isReloading}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Reload
<RefreshCcw className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<RefreshCcw className="size-4 mr-1" />
Reload
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -164,13 +161,14 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
<Button
variant="secondary"
isLoading={isStarting}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Start
<CheckCircle2 className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<CheckCircle2 className="size-4 mr-1" />
Start
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -203,13 +201,14 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
<Button
variant="destructive"
isLoading={isStopping}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Stop
<Ban className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Ban className="size-4 mr-1" />
Stop
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -225,9 +224,23 @@ export const ShowGeneralMongo = ({ mongoId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
<Button variant="outline">
<Terminal />
Open Terminal
<Button
variant="outline"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center">
<Terminal className="size-4 mr-1" />
Open Terminal
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Open a terminal to the MongoDB container</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DockerTerminalModal>
</CardContent>

View File

@@ -111,7 +111,10 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link href="/dashboard/settings" className="text-primary">
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}

View File

@@ -10,13 +10,7 @@ import {
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,
HelpCircle,
RefreshCcw,
Terminal,
} from "lucide-react";
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
@@ -77,7 +71,7 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
<CardContent className="flex flex-row gap-4 flex-wrap">
<TooltipProvider delayDuration={0}>
<DialogAction
title="Deploy Mysql"
title="Deploy MySQL"
description="Are you sure you want to deploy this mysql?"
type="default"
onClick={async () => {
@@ -89,12 +83,14 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
<Button
variant="default"
isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Deploy
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Rocket className="size-4 mr-1" />
Deploy
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -105,7 +101,7 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
</Button>
</DialogAction>
<DialogAction
title="Reload Mysql"
title="Reload MySQL"
description="Are you sure you want to reload this mysql?"
type="default"
onClick={async () => {
@@ -114,24 +110,25 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
appName: data?.appName || "",
})
.then(() => {
toast.success("Mysql reloaded successfully");
toast.success("MySQL reloaded successfully");
refetch();
})
.catch(() => {
toast.error("Error reloading Mysql");
toast.error("Error reloading MySQL");
});
}}
>
<Button
variant="secondary"
isLoading={isReloading}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Reload
<RefreshCcw className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<RefreshCcw className="size-4 mr-1" />
Reload
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -143,7 +140,7 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
</DialogAction>
{data?.applicationStatus === "idle" ? (
<DialogAction
title="Start Mysql"
title="Start MySQL"
description="Are you sure you want to start this mysql?"
type="default"
onClick={async () => {
@@ -151,24 +148,25 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
mysqlId: mysqlId,
})
.then(() => {
toast.success("Mysql started successfully");
toast.success("MySQL started successfully");
refetch();
})
.catch(() => {
toast.error("Error starting Mysql");
toast.error("Error starting MySQL");
});
}}
>
<Button
variant="secondary"
isLoading={isStarting}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Start
<CheckCircle2 className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<CheckCircle2 className="size-4 mr-1" />
Start
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -183,31 +181,32 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
</DialogAction>
) : (
<DialogAction
title="Stop Mysql"
title="Stop MySQL"
description="Are you sure you want to stop this mysql?"
onClick={async () => {
await stop({
mysqlId: mysqlId,
})
.then(() => {
toast.success("Mysql stopped successfully");
toast.success("MySQL stopped successfully");
refetch();
})
.catch(() => {
toast.error("Error stopping Mysql");
toast.error("Error stopping MySQL");
});
}}
>
<Button
variant="destructive"
isLoading={isStopping}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Stop
<Ban className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Ban className="size-4 mr-1" />
Stop
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -223,9 +222,23 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
<Button variant="outline">
<Terminal />
Open Terminal
<Button
variant="outline"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center">
<Terminal className="size-4 mr-1" />
Open Terminal
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Open a terminal to the MySQL container</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DockerTerminalModal>
</CardContent>

View File

@@ -113,7 +113,10 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link href="/dashboard/settings" className="text-primary">
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}

View File

@@ -10,13 +10,7 @@ import {
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,
HelpCircle,
RefreshCcw,
Terminal,
} from "lucide-react";
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
@@ -78,9 +72,9 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
<CardTitle className="text-xl">Deploy Settings</CardTitle>
</CardHeader>
<CardContent className="flex flex-row gap-4 flex-wrap">
<TooltipProvider delayDuration={0}>
<TooltipProvider disableHoverableContent={false}>
<DialogAction
title="Deploy Postgres"
title="Deploy PostgreSQL"
description="Are you sure you want to deploy this postgres?"
type="default"
onClick={async () => {
@@ -92,12 +86,14 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
<Button
variant="default"
isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Deploy
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Rocket className="size-4 mr-1" />
Deploy
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -108,7 +104,7 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
</Button>
</DialogAction>
<DialogAction
title="Reload Postgres"
title="Reload PostgreSQL"
description="Are you sure you want to reload this postgres?"
type="default"
onClick={async () => {
@@ -117,24 +113,25 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
appName: data?.appName || "",
})
.then(() => {
toast.success("Postgres reloaded successfully");
toast.success("PostgreSQL reloaded successfully");
refetch();
})
.catch(() => {
toast.error("Error reloading Postgres");
toast.error("Error reloading PostgreSQL");
});
}}
>
<Button
variant="secondary"
isLoading={isReloading}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Reload
<RefreshCcw className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<RefreshCcw className="size-4 mr-1" />
Reload
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -146,7 +143,7 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
</DialogAction>
{data?.applicationStatus === "idle" ? (
<DialogAction
title="Start Postgres"
title="Start PostgreSQL"
description="Are you sure you want to start this postgres?"
type="default"
onClick={async () => {
@@ -154,24 +151,25 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
postgresId: postgresId,
})
.then(() => {
toast.success("Postgres started successfully");
toast.success("PostgreSQL started successfully");
refetch();
})
.catch(() => {
toast.error("Error starting Postgres");
toast.error("Error starting PostgreSQL");
});
}}
>
<Button
variant="secondary"
isLoading={isStarting}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Start
<CheckCircle2 className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<CheckCircle2 className="size-4 mr-1" />
Start
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -186,31 +184,32 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
</DialogAction>
) : (
<DialogAction
title="Stop Postgres"
title="Stop PostgreSQL"
description="Are you sure you want to stop this postgres?"
onClick={async () => {
await stop({
postgresId: postgresId,
})
.then(() => {
toast.success("Postgres stopped successfully");
toast.success("PostgreSQL stopped successfully");
refetch();
})
.catch(() => {
toast.error("Error stopping Postgres");
toast.error("Error stopping PostgreSQL");
});
}}
>
<Button
variant="destructive"
isLoading={isStopping}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Stop
<Ban className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Ban className="size-4 mr-1" />
Stop
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -226,9 +225,23 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
<Button variant="outline">
<Terminal />
Open Terminal
<Button
variant="outline"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center">
<Terminal className="size-4 mr-1" />
Open Terminal
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Open a terminal to the PostgreSQL container</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DockerTerminalModal>
</CardContent>

View File

@@ -21,7 +21,7 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { PenBoxIcon } from "lucide-react";
import { PenBox } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -94,9 +94,9 @@ export const UpdatePostgres = ({ postgresId }: Props) => {
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 "
className="group hover:bg-blue-500/10 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
<PenBox className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
@@ -151,6 +151,7 @@ export const UpdatePostgres = ({ postgresId }: Props) => {
isLoading={isLoading}
form="hook-form-update-postgres"
type="submit"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Update
</Button>

View File

@@ -22,7 +22,7 @@ const examples = [
"Sendgrid service opensource analogue",
];
export const StepOne = ({ nextStep, setTemplateInfo, templateInfo }: any) => {
export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
// Get servers from the API
const { data: servers } = api.server.withSSHKey.useQuery();

View File

@@ -105,7 +105,10 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
{!getIp && (
<AlertBlock type="warning">
You need to set an IP address in your{" "}
<Link href="/dashboard/settings" className="text-primary">
<Link
href="/dashboard/settings/server"
className="text-primary"
>
{data?.serverId
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
: "Web Server -> Server -> Update Server IP"}

View File

@@ -10,13 +10,7 @@ import {
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
Ban,
CheckCircle2,
HelpCircle,
RefreshCcw,
Terminal,
} from "lucide-react";
import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
@@ -91,12 +85,14 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
<Button
variant="default"
isLoading={data?.applicationStatus === "running"}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Deploy
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Rocket className="size-4 mr-1" />
Deploy
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -127,13 +123,14 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
<Button
variant="secondary"
isLoading={isReloading}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Reload
<RefreshCcw className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<RefreshCcw className="size-4 mr-1" />
Reload
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -164,13 +161,14 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
<Button
variant="secondary"
isLoading={isStarting}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Start
<CheckCircle2 className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<CheckCircle2 className="size-4 mr-1" />
Start
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -203,13 +201,14 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
<Button
variant="destructive"
isLoading={isStopping}
className="flex items-center gap-1.5"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
Stop
<Ban className="size-4" />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="size-4 text-muted-foreground hover:text-foreground transition-colors cursor-pointer" />
<div className="flex items-center">
<Ban className="size-4 mr-1" />
Stop
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
@@ -225,9 +224,23 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
<Button variant="outline">
<Terminal />
Open Terminal
<Button
variant="outline"
className="flex items-center gap-1.5 focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center">
<Terminal className="size-4 mr-1" />
Open Terminal
</div>
</TooltipTrigger>
<TooltipPrimitive.Portal>
<TooltipContent sideOffset={5} className="z-[60]">
<p>Open a terminal to the Redis container</p>
</TooltipContent>
</TooltipPrimitive.Portal>
</Tooltip>
</Button>
</DockerTerminalModal>
</CardContent>

View File

@@ -56,10 +56,10 @@ export const AddNode = ({ serverId }: Props) => {
<TabsTrigger value="worker">Worker</TabsTrigger>
<TabsTrigger value="manager">Manager</TabsTrigger>
</TabsList>
<TabsContent value="worker" className="pt-4">
<TabsContent value="worker" className="pt-4 overflow-hidden">
<AddWorker serverId={serverId} />
</TabsContent>
<TabsContent value="manager" className="pt-4">
<TabsContent value="manager" className="pt-4 overflow-hidden">
<AddManager serverId={serverId} />
</TabsContent>
</Tabs>

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { CardContent } from "@/components/ui/card";
import {
DialogDescription,
@@ -6,7 +7,7 @@ import {
} from "@/components/ui/dialog";
import { api } from "@/utils/api";
import copy from "copy-to-clipboard";
import { CopyIcon } from "lucide-react";
import { CopyIcon, Loader2 } from "lucide-react";
import { toast } from "sonner";
interface Props {
@@ -14,56 +15,66 @@ interface Props {
}
export const AddManager = ({ serverId }: Props) => {
const { data } = api.cluster.addManager.useQuery({ serverId });
const { data, isLoading, error, isError } = api.cluster.addManager.useQuery({
serverId,
});
return (
<>
<div>
<CardContent className="sm:max-w-4xl max-h-screen overflow-y-auto flex flex-col gap-4 px-0">
<DialogHeader>
<DialogTitle>Add a new manager</DialogTitle>
<DialogDescription>Add a new manager</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-2.5 text-sm">
<span>1. Go to your new server and run the following command</span>
<span className="bg-muted rounded-lg p-2 flex justify-between">
curl https://get.docker.com | sh -s -- --version {data?.version}
<button
type="button"
className="self-center"
onClick={() => {
copy(
`curl https://get.docker.com | sh -s -- --version ${data?.version}`,
);
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
<CardContent className="sm:max-w-4xl flex flex-col gap-4 px-0">
<DialogHeader>
<DialogTitle>Add a new manager</DialogTitle>
<DialogDescription>Add a new manager</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
{isLoading ? (
<Loader2 className="w-full animate-spin text-muted-foreground" />
) : (
<>
<div className="flex flex-col gap-2.5 text-sm">
<span>
1. Go to your new server and run the following command
</span>
<span className="bg-muted rounded-lg p-2 flex justify-between">
curl https://get.docker.com | sh -s -- --version {data?.version}
<button
type="button"
className="self-center"
onClick={() => {
copy(
`curl https://get.docker.com | sh -s -- --version ${data?.version}`,
);
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
<div className="flex flex-col gap-2.5 text-sm">
<span>
2. Run the following command to add the node(manager) to your
cluster
</span>
<span className="bg-muted rounded-lg p-2 flex">
{data?.command}
<button
type="button"
className="self-start"
onClick={() => {
copy(data?.command || "");
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
</CardContent>
</div>
<div className="flex flex-col gap-2.5 text-sm">
<span>
2. Run the following command to add the node(manager) to your
cluster
</span>
<span className="bg-muted rounded-lg p-2 flex">
{data?.command}
<button
type="button"
className="self-start"
onClick={() => {
copy(data?.command || "");
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
</>
)}
</CardContent>
</>
);
};

View File

@@ -17,7 +17,7 @@ export const ShowNodesModal = ({ serverId }: Props) => {
className="w-full cursor-pointer "
onSelect={(e) => e.preventDefault()}
>
Show Nodes
Show Swarm Nodes
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className="sm:max-w-5xl overflow-y-auto max-h-screen ">

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { CardContent } from "@/components/ui/card";
import {
DialogDescription,
@@ -6,7 +7,7 @@ import {
} from "@/components/ui/dialog";
import { api } from "@/utils/api";
import copy from "copy-to-clipboard";
import { CopyIcon } from "lucide-react";
import { CopyIcon, Loader2 } from "lucide-react";
import { toast } from "sonner";
interface Props {
@@ -14,54 +15,62 @@ interface Props {
}
export const AddWorker = ({ serverId }: Props) => {
const { data } = api.cluster.addWorker.useQuery({ serverId });
const { data, isLoading, error, isError } = api.cluster.addWorker.useQuery({
serverId,
});
return (
<div>
<CardContent className="sm:max-w-4xl max-h-screen overflow-y-auto flex flex-col gap-4 px-0">
<DialogHeader>
<DialogTitle>Add a new worker</DialogTitle>
<DialogDescription>Add a new worker</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-2.5 text-sm">
<span>1. Go to your new server and run the following command</span>
<span className="bg-muted rounded-lg p-2 flex justify-between">
curl https://get.docker.com | sh -s -- --version {data?.version}
<button
type="button"
className="self-center"
onClick={() => {
copy(
`curl https://get.docker.com | sh -s -- --version ${data?.version}`,
);
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
<CardContent className="sm:max-w-4xl flex flex-col gap-4 px-0">
<DialogHeader>
<DialogTitle>Add a new worker</DialogTitle>
<DialogDescription>Add a new worker</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
{isLoading ? (
<Loader2 className="w-full animate-spin text-muted-foreground" />
) : (
<>
<div className="flex flex-col gap-2.5 text-sm">
<span>1. Go to your new server and run the following command</span>
<span className="bg-muted rounded-lg p-2 flex justify-between">
curl https://get.docker.com | sh -s -- --version {data?.version}
<button
type="button"
className="self-center"
onClick={() => {
copy(
`curl https://get.docker.com | sh -s -- --version ${data?.version}`,
);
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
<div className="flex flex-col gap-2.5 text-sm">
<span>
2. Run the following command to add the node(worker) to your cluster
</span>
<div className="flex flex-col gap-2.5 text-sm">
<span>
2. Run the following command to add the node(worker) to your
cluster
</span>
<span className="bg-muted rounded-lg p-2 flex">
{data?.command}
<button
type="button"
className="self-start"
onClick={() => {
copy(data?.command || "");
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
</CardContent>
</div>
<span className="bg-muted rounded-lg p-2 flex">
{data?.command}
<button
type="button"
className="self-start"
onClick={() => {
copy(data?.command || "");
toast.success("Copied to clipboard");
}}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
</>
)}
</CardContent>
);
};

View File

@@ -139,7 +139,10 @@ export const AddGiteaProvider = () => {
<ExternalLink className="w-fit text-primary size-4" />
</Link>
</li>
<li>Navigate to Applications</li>
<li>
Navigate to Applications {"->"} Create new OAuth2
Application
</li>
<li>
Create a new application with the following details:
<ul className="list-disc list-inside ml-4">
@@ -148,10 +151,6 @@ export const AddGiteaProvider = () => {
Redirect URI:{" "}
<span className="text-primary">{webhookUrl}</span>{" "}
</li>
<li>
Select Permissions - organization: read, user: read,
repository: read/write
</li>
</ul>
</li>
<li>

View File

@@ -124,10 +124,10 @@ export const ShowGitProviders = () => {
gitProvider.gitlab?.accessToken &&
gitProvider.gitlab?.refreshToken;
const haveGiteaRequirements =
isGitea &&
gitProvider.gitea?.accessToken &&
gitProvider.gitea?.refreshToken;
// const haveGiteaRequirements =
// isGitea &&
// gitProvider.gitea?.accessToken &&
// gitProvider.gitea?.refreshToken;
return (
<div
@@ -207,6 +207,7 @@ export const ShowGitProviders = () => {
</Link>
</div>
)}
{isGithub && haveGithubRequirements && (
<EditGithubProvider
githubId={gitProvider.github?.githubId}

View File

@@ -663,13 +663,16 @@ export const HandleNotifications = ({ notificationId }: Props) => {
{...field}
onChange={(e) => {
const value = e.target.value;
if (value) {
if (value === "") {
field.onChange(undefined);
} else {
const port = Number.parseInt(value);
if (port > 0 && port < 65536) {
field.onChange(port);
}
}
}}
value={field.value || ""}
type="number"
/>
</FormControl>

View File

@@ -159,9 +159,11 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
<Input
type="number"
{...field}
onChange={(e) =>
field.onChange(Number(e.target.value))
}
onChange={(e) => {
const value = e.target.value;
field.onChange(value === "" ? undefined : Number(value));
}}
value={field.value || ""}
className="w-full dark:bg-black"
placeholder="e.g. 8080"
/>
@@ -185,9 +187,11 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
<Input
type="number"
{...field}
onChange={(e) =>
field.onChange(Number(e.target.value))
}
onChange={(e) => {
const value = e.target.value;
field.onChange(value === "" ? undefined : Number(value));
}}
value={field.value || ""}
className="w-full dark:bg-black"
placeholder="e.g. 80"
/>

View File

@@ -1,16 +0,0 @@
ALTER TYPE "public"."gitProviderType" ADD VALUE 'gitea';--> statement-breakpoint
CREATE TABLE "gitea" (
"giteaId" text PRIMARY KEY NOT NULL,
"giteaUrl" text DEFAULT 'https://gitea.com' NOT NULL,
"application_id" text,
"redirect_uri" text,
"secret" text,
"access_token" text,
"refresh_token" text,
"organization_name" text,
"expires_at" integer,
"gitProviderId" text NOT NULL,
"gitea_username" text
);
--> statement-breakpoint
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;

View File

@@ -1,9 +0,0 @@
ALTER TYPE "public"."sourceType" ADD VALUE 'gitea' BEFORE 'drop';--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaProjectId" integer;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaRepository" text;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaOwner" text;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaBranch" text;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaBuildPath" text DEFAULT '/';--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaPathNamespace" text;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaId" text;--> statement-breakpoint
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;

View File

@@ -1,6 +0,0 @@
ALTER TABLE "gitea" ADD COLUMN "client_id" text;--> statement-breakpoint
ALTER TABLE "gitea" ADD COLUMN "client_secret" text;--> statement-breakpoint
ALTER TABLE "gitea" DROP COLUMN "application_id";--> statement-breakpoint
ALTER TABLE "gitea" DROP COLUMN "secret";--> statement-breakpoint
ALTER TABLE "gitea" DROP COLUMN "refresh_token";--> statement-breakpoint
ALTER TABLE "gitea" DROP COLUMN "organization_name";

View File

@@ -1,4 +0,0 @@
ALTER TABLE "gitea" ADD COLUMN "refresh_token" text;--> statement-breakpoint
ALTER TABLE "gitea" ADD COLUMN "organization_name" text;--> statement-breakpoint
ALTER TABLE "gitea" ADD COLUMN "scopes" text;--> statement-breakpoint
ALTER TABLE "gitea" ADD COLUMN "last_authenticated_at" integer;

View File

@@ -1,6 +0,0 @@
ALTER TYPE "public"."sourceTypeCompose" ADD VALUE 'gitea' BEFORE 'raw';--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "giteaRepository" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "giteaOwner" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "giteaBranch" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "giteaId" text;--> statement-breakpoint
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;

View File

@@ -1,157 +0,0 @@
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

@@ -0,0 +1,32 @@
ALTER TYPE "public"."sourceType" ADD VALUE 'gitea' BEFORE 'drop';--> statement-breakpoint
ALTER TYPE "public"."sourceTypeCompose" ADD VALUE 'gitea' BEFORE 'raw';--> statement-breakpoint
ALTER TYPE "public"."gitProviderType" ADD VALUE 'gitea';--> statement-breakpoint
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
);
--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaProjectId" integer;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaRepository" text;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaOwner" text;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaBranch" text;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaBuildPath" text DEFAULT '/';--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaPathNamespace" text;--> statement-breakpoint
ALTER TABLE "application" ADD COLUMN "giteaId" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "giteaRepository" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "giteaOwner" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "giteaBranch" text;--> statement-breakpoint
ALTER TABLE "compose" ADD COLUMN "giteaId" text;--> statement-breakpoint
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;--> statement-breakpoint
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;--> statement-breakpoint
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;

View File

@@ -1,5 +1,5 @@
{
"id": "436368c7-e9a2-4df0-8c8f-863a69c9432d",
"id": "44cb886c-d31a-4b3d-b70e-da306c74dcf5",
"prevId": "53fc370e-731b-4bfe-874f-9ce725650082",
"version": "7",
"dialect": "postgresql",
@@ -868,6 +868,12 @@
"notNull": true,
"default": false
},
"logCleanupCron": {
"name": "logCleanupCron",
"type": "text",
"primaryKey": false,
"notNull": false
},
"enablePaidFeatures": {
"name": "enablePaidFeatures",
"type": "boolean",
@@ -3914,100 +3920,6 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.gitea": {
"name": "gitea",
"schema": "",
"columns": {
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": true,
"notNull": true
},
"giteaUrl": {
"name": "giteaUrl",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'https://gitea.com'"
},
"application_id": {
"name": "application_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"redirect_uri": {
"name": "redirect_uri",
"type": "text",
"primaryKey": false,
"notNull": false
},
"secret": {
"name": "secret",
"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
},
"organization_name": {
"name": "organization_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"gitProviderId": {
"name": "gitProviderId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"gitea_username": {
"name": "gitea_username",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"gitea_gitProviderId_git_provider_gitProviderId_fk": {
"name": "gitea_gitProviderId_git_provider_gitProviderId_fk",
"tableFrom": "gitea",
"tableTo": "git_provider",
"columnsFrom": [
"gitProviderId"
],
"columnsTo": [
"gitProviderId"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.server": {
"name": "server",
"schema": "",
@@ -5195,8 +5107,7 @@
"values": [
"github",
"gitlab",
"bitbucket",
"gitea"
"bitbucket"
]
},
"public.serverStatus": {

View File

@@ -1,6 +1,6 @@
{
"id": "4360a233-2c88-4d6c-834f-eef562e079e3",
"prevId": "436368c7-e9a2-4df0-8c8f-863a69c9432d",
"id": "ad43c733-01c3-4841-b600-252421350fb9",
"prevId": "44cb886c-d31a-4b3d-b70e-da306c74dcf5",
"version": "7",
"dialect": "postgresql",
"tables": {
@@ -235,43 +235,6 @@
"primaryKey": false,
"notNull": false
},
"giteaProjectId": {
"name": "giteaProjectId",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"giteaRepository": {
"name": "giteaRepository",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaOwner": {
"name": "giteaOwner",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBranch": {
"name": "giteaBranch",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBuildPath": {
"name": "giteaBuildPath",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'/'"
},
"giteaPathNamespace": {
"name": "giteaPathNamespace",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketRepository": {
"name": "bitbucketRepository",
"type": "text",
@@ -483,12 +446,6 @@
"primaryKey": false,
"notNull": false
},
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketId": {
"name": "bitbucketId",
"type": "text",
@@ -569,19 +526,6 @@
"onDelete": "set null",
"onUpdate": "no action"
},
"application_giteaId_gitea_giteaId_fk": {
"name": "application_giteaId_gitea_giteaId_fk",
"tableFrom": "application",
"tableTo": "gitea",
"columnsFrom": [
"giteaId"
],
"columnsTo": [
"giteaId"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"application_bitbucketId_bitbucket_bitbucketId_fk": {
"name": "application_bitbucketId_bitbucket_bitbucketId_fk",
"tableFrom": "application",
@@ -917,13 +861,6 @@
"notNull": true,
"default": false
},
"enableLogRotation": {
"name": "enableLogRotation",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"logCleanupCron": {
"name": "logCleanupCron",
"type": "text",
@@ -1153,6 +1090,12 @@
"primaryKey": false,
"notNull": true,
"default": "'none'"
},
"customCertResolver": {
"name": "customCertResolver",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
@@ -3976,100 +3919,6 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.gitea": {
"name": "gitea",
"schema": "",
"columns": {
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": true,
"notNull": true
},
"giteaUrl": {
"name": "giteaUrl",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'https://gitea.com'"
},
"application_id": {
"name": "application_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"redirect_uri": {
"name": "redirect_uri",
"type": "text",
"primaryKey": false,
"notNull": false
},
"secret": {
"name": "secret",
"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
},
"organization_name": {
"name": "organization_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"gitProviderId": {
"name": "gitProviderId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"gitea_username": {
"name": "gitea_username",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"gitea_gitProviderId_git_provider_gitProviderId_fk": {
"name": "gitea_gitProviderId_git_provider_gitProviderId_fk",
"tableFrom": "gitea",
"tableTo": "git_provider",
"columnsFrom": [
"gitProviderId"
],
"columnsTo": [
"gitProviderId"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.server": {
"name": "server",
"schema": "",
@@ -5134,7 +4983,6 @@
"github",
"gitlab",
"bitbucket",
"gitea",
"drop"
]
},
@@ -5259,8 +5107,7 @@
"values": [
"github",
"gitlab",
"bitbucket",
"gitea"
"bitbucket"
]
},
"public.serverStatus": {

View File

@@ -1,6 +1,6 @@
{
"id": "8946105c-e69c-49c7-8334-784ee2419a92",
"prevId": "4360a233-2c88-4d6c-834f-eef562e079e3",
"id": "5808aed9-4223-4ac4-ae22-6c0dae500d75",
"prevId": "ad43c733-01c3-4841-b600-252421350fb9",
"version": "7",
"dialect": "postgresql",
"tables": {
@@ -241,43 +241,6 @@
"primaryKey": false,
"notNull": false
},
"giteaProjectId": {
"name": "giteaProjectId",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"giteaRepository": {
"name": "giteaRepository",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaOwner": {
"name": "giteaOwner",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBranch": {
"name": "giteaBranch",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBuildPath": {
"name": "giteaBuildPath",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'/'"
},
"giteaPathNamespace": {
"name": "giteaPathNamespace",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketRepository": {
"name": "bitbucketRepository",
"type": "text",
@@ -489,12 +452,6 @@
"primaryKey": false,
"notNull": false
},
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketId": {
"name": "bitbucketId",
"type": "text",
@@ -575,19 +532,6 @@
"onDelete": "set null",
"onUpdate": "no action"
},
"application_giteaId_gitea_giteaId_fk": {
"name": "application_giteaId_gitea_giteaId_fk",
"tableFrom": "application",
"tableTo": "gitea",
"columnsFrom": [
"giteaId"
],
"columnsTo": [
"giteaId"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"application_bitbucketId_bitbucket_bitbucketId_fk": {
"name": "application_bitbucketId_bitbucket_bitbucketId_fk",
"tableFrom": "application",
@@ -923,13 +867,6 @@
"notNull": true,
"default": false
},
"enableLogRotation": {
"name": "enableLogRotation",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"logCleanupCron": {
"name": "logCleanupCron",
"type": "text",
@@ -3988,88 +3925,6 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.gitea": {
"name": "gitea",
"schema": "",
"columns": {
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": true,
"notNull": true
},
"giteaUrl": {
"name": "giteaUrl",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'https://gitea.com'"
},
"redirect_uri": {
"name": "redirect_uri",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_id": {
"name": "client_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_secret": {
"name": "client_secret",
"type": "text",
"primaryKey": false,
"notNull": false
},
"gitProviderId": {
"name": "gitProviderId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"gitea_username": {
"name": "gitea_username",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"gitea_gitProviderId_git_provider_gitProviderId_fk": {
"name": "gitea_gitProviderId_git_provider_gitProviderId_fk",
"tableFrom": "gitea",
"tableTo": "git_provider",
"columnsFrom": [
"gitProviderId"
],
"columnsTo": [
"gitProviderId"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.server": {
"name": "server",
"schema": "",
@@ -5134,7 +4989,6 @@
"github",
"gitlab",
"bitbucket",
"gitea",
"drop"
]
},
@@ -5259,8 +5113,7 @@
"values": [
"github",
"gitlab",
"bitbucket",
"gitea"
"bitbucket"
]
},
"public.serverStatus": {

View File

@@ -1,6 +1,6 @@
{
"id": "85133e87-6886-493f-89d6-c83162f76d50",
"prevId": "8946105c-e69c-49c7-8334-784ee2419a92",
"id": "3584a243-55f6-418b-aece-985d878b30d7",
"prevId": "5808aed9-4223-4ac4-ae22-6c0dae500d75",
"version": "7",
"dialect": "postgresql",
"tables": {
@@ -241,43 +241,6 @@
"primaryKey": false,
"notNull": false
},
"giteaProjectId": {
"name": "giteaProjectId",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"giteaRepository": {
"name": "giteaRepository",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaOwner": {
"name": "giteaOwner",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBranch": {
"name": "giteaBranch",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBuildPath": {
"name": "giteaBuildPath",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'/'"
},
"giteaPathNamespace": {
"name": "giteaPathNamespace",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketRepository": {
"name": "bitbucketRepository",
"type": "text",
@@ -489,12 +452,6 @@
"primaryKey": false,
"notNull": false
},
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketId": {
"name": "bitbucketId",
"type": "text",
@@ -575,19 +532,6 @@
"onDelete": "set null",
"onUpdate": "no action"
},
"application_giteaId_gitea_giteaId_fk": {
"name": "application_giteaId_gitea_giteaId_fk",
"tableFrom": "application",
"tableTo": "gitea",
"columnsFrom": [
"giteaId"
],
"columnsTo": [
"giteaId"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"application_bitbucketId_bitbucket_bitbucketId_fk": {
"name": "application_bitbucketId_bitbucket_bitbucketId_fk",
"tableFrom": "application",
@@ -923,13 +867,6 @@
"notNull": true,
"default": false
},
"enableLogRotation": {
"name": "enableLogRotation",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"logCleanupCron": {
"name": "logCleanupCron",
"type": "text",
@@ -3988,112 +3925,6 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.gitea": {
"name": "gitea",
"schema": "",
"columns": {
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": true,
"notNull": true
},
"giteaUrl": {
"name": "giteaUrl",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'https://gitea.com'"
},
"redirect_uri": {
"name": "redirect_uri",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_id": {
"name": "client_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_secret": {
"name": "client_secret",
"type": "text",
"primaryKey": false,
"notNull": false
},
"gitProviderId": {
"name": "gitProviderId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"gitea_username": {
"name": "gitea_username",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"organization_name": {
"name": "organization_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"scopes": {
"name": "scopes",
"type": "text",
"primaryKey": false,
"notNull": false
},
"last_authenticated_at": {
"name": "last_authenticated_at",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"gitea_gitProviderId_git_provider_gitProviderId_fk": {
"name": "gitea_gitProviderId_git_provider_gitProviderId_fk",
"tableFrom": "gitea",
"tableTo": "git_provider",
"columnsFrom": [
"gitProviderId"
],
"columnsTo": [
"gitProviderId"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.server": {
"name": "server",
"schema": "",
@@ -5158,7 +4989,6 @@
"github",
"gitlab",
"bitbucket",
"gitea",
"drop"
]
},
@@ -5283,8 +5113,7 @@
"values": [
"github",
"gitlab",
"bitbucket",
"gitea"
"bitbucket"
]
},
"public.serverStatus": {

View File

@@ -1,6 +1,6 @@
{
"id": "00d7b47b-5341-4dda-b121-4a87cec39d95",
"prevId": "85133e87-6886-493f-89d6-c83162f76d50",
"id": "dd51ff04-a160-4d0d-b72f-4916493b740f",
"prevId": "3584a243-55f6-418b-aece-985d878b30d7",
"version": "7",
"dialect": "postgresql",
"tables": {
@@ -247,43 +247,6 @@
"primaryKey": false,
"notNull": false
},
"giteaProjectId": {
"name": "giteaProjectId",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"giteaRepository": {
"name": "giteaRepository",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaOwner": {
"name": "giteaOwner",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBranch": {
"name": "giteaBranch",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBuildPath": {
"name": "giteaBuildPath",
"type": "text",
"primaryKey": false,
"notNull": false,
"default": "'/'"
},
"giteaPathNamespace": {
"name": "giteaPathNamespace",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketRepository": {
"name": "bitbucketRepository",
"type": "text",
@@ -495,12 +458,6 @@
"primaryKey": false,
"notNull": false
},
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"bitbucketId": {
"name": "bitbucketId",
"type": "text",
@@ -581,19 +538,6 @@
"onDelete": "set null",
"onUpdate": "no action"
},
"application_giteaId_gitea_giteaId_fk": {
"name": "application_giteaId_gitea_giteaId_fk",
"tableFrom": "application",
"tableTo": "gitea",
"columnsFrom": [
"giteaId"
],
"columnsTo": [
"giteaId"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"application_bitbucketId_bitbucket_bitbucketId_fk": {
"name": "application_bitbucketId_bitbucket_bitbucketId_fk",
"tableFrom": "application",
@@ -929,13 +873,6 @@
"notNull": true,
"default": false
},
"enableLogRotation": {
"name": "enableLogRotation",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"logCleanupCron": {
"name": "logCleanupCron",
"type": "text",
@@ -2971,24 +2908,6 @@
"primaryKey": false,
"notNull": false
},
"giteaRepository": {
"name": "giteaRepository",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaOwner": {
"name": "giteaOwner",
"type": "text",
"primaryKey": false,
"notNull": false
},
"giteaBranch": {
"name": "giteaBranch",
"type": "text",
"primaryKey": false,
"notNull": false
},
"customGitUrl": {
"name": "customGitUrl",
"type": "text",
@@ -3080,12 +2999,6 @@
"primaryKey": false,
"notNull": false
},
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": false,
"notNull": false
},
"serverId": {
"name": "serverId",
"type": "text",
@@ -3160,19 +3073,6 @@
"onDelete": "set null",
"onUpdate": "no action"
},
"compose_giteaId_gitea_giteaId_fk": {
"name": "compose_giteaId_gitea_giteaId_fk",
"tableFrom": "compose",
"tableTo": "gitea",
"columnsFrom": [
"giteaId"
],
"columnsTo": [
"giteaId"
],
"onDelete": "set null",
"onUpdate": "no action"
},
"compose_serverId_server_serverId_fk": {
"name": "compose_serverId_server_serverId_fk",
"tableFrom": "compose",
@@ -4031,112 +3931,6 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.gitea": {
"name": "gitea",
"schema": "",
"columns": {
"giteaId": {
"name": "giteaId",
"type": "text",
"primaryKey": true,
"notNull": true
},
"giteaUrl": {
"name": "giteaUrl",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'https://gitea.com'"
},
"redirect_uri": {
"name": "redirect_uri",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_id": {
"name": "client_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"client_secret": {
"name": "client_secret",
"type": "text",
"primaryKey": false,
"notNull": false
},
"gitProviderId": {
"name": "gitProviderId",
"type": "text",
"primaryKey": false,
"notNull": true
},
"gitea_username": {
"name": "gitea_username",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"organization_name": {
"name": "organization_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"scopes": {
"name": "scopes",
"type": "text",
"primaryKey": false,
"notNull": false
},
"last_authenticated_at": {
"name": "last_authenticated_at",
"type": "integer",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"gitea_gitProviderId_git_provider_gitProviderId_fk": {
"name": "gitea_gitProviderId_git_provider_gitProviderId_fk",
"tableFrom": "gitea",
"tableTo": "git_provider",
"columnsFrom": [
"gitProviderId"
],
"columnsTo": [
"gitProviderId"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.server": {
"name": "server",
"schema": "",
@@ -5201,7 +4995,6 @@
"github",
"gitlab",
"bitbucket",
"gitea",
"drop"
]
},
@@ -5298,7 +5091,6 @@
"github",
"gitlab",
"bitbucket",
"gitea",
"raw"
]
},
@@ -5327,8 +5119,7 @@
"values": [
"github",
"gitlab",
"bitbucket",
"gitea"
"bitbucket"
]
},
"public.serverStatus": {

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -502,64 +502,64 @@
{
"idx": 71,
"version": "7",
"when": 1741559743256,
"tag": "0071_flimsy_plazm",
"when": 1741460060541,
"tag": "0071_flaky_black_queen",
"breakpoints": true
},
{
"idx": 72,
"version": "7",
"when": 1741593124105,
"tag": "0072_low_redwing",
"when": 1741487009559,
"tag": "0072_green_susan_delgado",
"breakpoints": true
},
{
"idx": 73,
"version": "7",
"when": 1741645208694,
"tag": "0073_dark_tigra",
"when": 1741489681190,
"tag": "0073_hot_domino",
"breakpoints": true
},
{
"idx": 74,
"version": "7",
"when": 1741673569715,
"tag": "0074_military_miss_america",
"when": 1741490064139,
"tag": "0074_black_quasar",
"breakpoints": true
},
{
"idx": 75,
"version": "7",
"when": 1742018928109,
"tag": "0075_wild_xorn",
"when": 1741491527516,
"tag": "0075_young_typhoid_mary",
"breakpoints": true
},
{
"idx": 76,
"version": "7",
"when": 1742237840762,
"tag": "0076_tough_iron_patriot",
"when": 1741493754270,
"tag": "0076_young_sharon_ventura",
"breakpoints": true
},
{
"idx": 77,
"version": "7",
"when": 1742241730000,
"tag": "0076_young_sharon_ventura",
"when": 1741510086231,
"tag": "0077_chemical_dreadnoughts",
"breakpoints": true
},
{
"idx": 78,
"version": "7",
"when": 1742241730001,
"tag": "0077_chemical_dreadnoughts",
"when": 1742112194375,
"tag": "0078_uneven_omega_sentinel",
"breakpoints": true
},
{
"idx": 79,
"version": "7",
"when": 17422417300002,
"tag": "0078_uneven_omega_sentinel",
"when": 1742281690186,
"tag": "0079_bizarre_wendell_rand",
"breakpoints": true
}
]

View File

@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.20.4",
"version": "v0.20.7",
"private": true,
"license": "Apache-2.0",
"type": "module",

View File

@@ -93,6 +93,7 @@ export default async function handler(
try {
const branchName = githubBody?.ref?.replace("refs/heads/", "");
const repository = githubBody?.repository?.name;
const deploymentTitle = extractCommitMessage(req.headers, req.body);
const deploymentHash = extractHash(req.headers, req.body);
const owner = githubBody?.repository?.owner?.name;
@@ -107,6 +108,7 @@ export default async function handler(
eq(applications.branch, branchName),
eq(applications.repository, repository),
eq(applications.owner, owner),
eq(applications.githubId, githubResult.githubId),
),
});
@@ -151,6 +153,7 @@ export default async function handler(
eq(compose.branch, branchName),
eq(compose.repository, repository),
eq(compose.owner, owner),
eq(compose.githubId, githubResult.githubId),
),
});
@@ -240,6 +243,7 @@ export default async function handler(
eq(applications.branch, branch),
eq(applications.isPreviewDeploymentsActive, true),
eq(applications.owner, owner),
eq(applications.githubId, githubResult.githubId),
),
with: {
previewDeployments: true,

View File

@@ -11,9 +11,13 @@ import {
createBackup,
findBackupById,
findMariadbByBackupId,
findMariadbById,
findMongoByBackupId,
findMongoById,
findMySqlByBackupId,
findMySqlById,
findPostgresByBackupId,
findPostgresById,
findServerById,
removeBackupById,
removeScheduleBackup,
@@ -25,7 +29,21 @@ import {
updateBackupById,
} from "@dokploy/server";
import { findDestinationById } from "@dokploy/server/services/destination";
import { getS3Credentials } from "@dokploy/server/utils/backups/utils";
import {
execAsync,
execAsyncRemote,
} from "@dokploy/server/utils/process/execAsync";
import {
restoreMariadbBackup,
restoreMongoBackup,
restoreMySqlBackup,
restorePostgresBackup,
} from "@dokploy/server/utils/restore";
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { z } from "zod";
export const backupRouter = createTRPCRouter({
create: protectedProcedure
@@ -209,27 +227,146 @@ export const backupRouter = createTRPCRouter({
});
}
}),
listBackupFiles: protectedProcedure
.input(
z.object({
destinationId: z.string(),
search: z.string(),
serverId: z.string().optional(),
}),
)
.query(async ({ input }) => {
try {
const destination = await findDestinationById(input.destinationId);
const rcloneFlags = getS3Credentials(destination);
const bucketPath = `:s3:${destination.bucket}`;
const lastSlashIndex = input.search.lastIndexOf("/");
const baseDir =
lastSlashIndex !== -1
? input.search.slice(0, lastSlashIndex + 1)
: "";
const searchTerm =
lastSlashIndex !== -1
? input.search.slice(lastSlashIndex + 1)
: input.search;
const searchPath = baseDir ? `${bucketPath}/${baseDir}` : bucketPath;
const listCommand = `rclone lsf ${rcloneFlags.join(" ")} "${searchPath}" | head -n 100`;
let stdout = "";
if (input.serverId) {
const result = await execAsyncRemote(listCommand, input.serverId);
stdout = result.stdout;
} else {
const result = await execAsync(listCommand);
stdout = result.stdout;
}
const files = stdout.split("\n").filter(Boolean);
const results = baseDir
? files.map((file) => `${baseDir}${file}`)
: files;
if (searchTerm) {
return results.filter((file) =>
file.toLowerCase().includes(searchTerm.toLowerCase()),
);
}
return results;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message:
error instanceof Error
? error.message
: "Error listing backup files",
cause: error,
});
}
}),
restoreBackupWithLogs: protectedProcedure
.meta({
openapi: {
enabled: false,
path: "/restore-backup-with-logs",
method: "POST",
override: true,
},
})
.input(
z.object({
databaseId: z.string(),
databaseType: z.enum(["postgres", "mysql", "mariadb", "mongo"]),
databaseName: z.string().min(1),
backupFile: z.string().min(1),
destinationId: z.string().min(1),
}),
)
.subscription(async ({ input }) => {
const destination = await findDestinationById(input.destinationId);
if (input.databaseType === "postgres") {
const postgres = await findPostgresById(input.databaseId);
return observable<string>((emit) => {
restorePostgresBackup(
postgres,
destination,
input.databaseName,
input.backupFile,
(log) => {
emit.next(log);
},
);
});
}
if (input.databaseType === "mysql") {
const mysql = await findMySqlById(input.databaseId);
return observable<string>((emit) => {
restoreMySqlBackup(
mysql,
destination,
input.databaseName,
input.backupFile,
(log) => {
emit.next(log);
},
);
});
}
if (input.databaseType === "mariadb") {
const mariadb = await findMariadbById(input.databaseId);
return observable<string>((emit) => {
restoreMariadbBackup(
mariadb,
destination,
input.databaseName,
input.backupFile,
(log) => {
emit.next(log);
},
);
});
}
if (input.databaseType === "mongo") {
const mongo = await findMongoById(input.databaseId);
return observable<string>((emit) => {
restoreMongoBackup(
mongo,
destination,
input.databaseName,
input.backupFile,
(log) => {
emit.next(log);
},
);
});
}
return true;
}),
});
// export const getAdminId = async (backupId: string) => {
// const backup = await findBackupById(backupId);
// if (backup.databaseType === "postgres" && backup.postgresId) {
// const postgres = await findPostgresById(backup.postgresId);
// return postgres.project.adminId;
// }
// if (backup.databaseType === "mariadb" && backup.mariadbId) {
// const mariadb = await findMariadbById(backup.mariadbId);
// return mariadb.project.adminId;
// }
// if (backup.databaseType === "mysql" && backup.mysqlId) {
// const mysql = await findMySqlById(backup.mysqlId);
// return mysql.project.adminId;
// }
// if (backup.databaseType === "mongo" && backup.mongoId) {
// const mongo = await findMongoById(backup.mongoId);
// return mongo.project.adminId;
// }
// return null;
// };

View File

@@ -13,7 +13,9 @@ import {
findDomainById,
findDomainsByApplicationId,
findDomainsByComposeId,
findOrganizationById,
findPreviewDeploymentById,
findServerById,
generateTraefikMeDomain,
manageDomain,
removeDomain,
@@ -94,6 +96,19 @@ export const domainRouter = createTRPCRouter({
input.serverId,
);
}),
canGenerateTraefikMeDomains: protectedProcedure
.input(z.object({ serverId: z.string() }))
.query(async ({ input, ctx }) => {
const organization = await findOrganizationById(
ctx.session.activeOrganizationId,
);
if (input.serverId) {
const server = await findServerById(input.serverId);
return server.ipAddress;
}
return organization?.owner.serverIp;
}),
update: protectedProcedure
.input(apiUpdateDomain)

View File

@@ -2,12 +2,16 @@ import type { IncomingMessage } from "node:http";
import * as bcrypt from "bcrypt";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { APIError } from "better-auth/api";
import { apiKey, organization, twoFactor } from "better-auth/plugins";
import { and, desc, eq } from "drizzle-orm";
import { IS_CLOUD } from "../constants";
import { db } from "../db";
import * as schema from "../db/schema";
import { getUserByToken } from "../services/admin";
import { updateUser } from "../services/user";
import { sendEmail } from "../verification/send-verification-email";
import { getPublicIpWithFallback } from "../wss/utils";
const { handler, api } = betterAuth({
database: drizzleAdapter(db, {
@@ -88,11 +92,40 @@ const { handler, api } = betterAuth({
databaseHooks: {
user: {
create: {
before: async (_user, context) => {
if (!IS_CLOUD) {
const xDokployToken =
context?.request?.headers?.get("x-dokploy-token");
if (xDokployToken) {
const user = await getUserByToken(xDokployToken);
if (!user) {
throw new APIError("BAD_REQUEST", {
message: "User not found",
});
}
} else {
const isAdminPresent = await db.query.member.findFirst({
where: eq(schema.member.role, "owner"),
});
if (isAdminPresent) {
throw new APIError("BAD_REQUEST", {
message: "Admin is already created",
});
}
}
}
},
after: async (user) => {
const isAdminPresent = await db.query.member.findFirst({
where: eq(schema.member.role, "owner"),
});
if (!IS_CLOUD) {
await updateUser(user.id, {
serverIp: await getPublicIpWithFallback(),
});
}
if (IS_CLOUD || !isAdminPresent) {
await db.transaction(async (tx) => {
const organization = await tx

View File

@@ -361,7 +361,7 @@ const installUtilities = () => `
alpine)
sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories
apk update >/dev/null
apk add curl wget git jq openssl >/dev/null
apk add curl wget git jq openssl sudo unzip tar >/dev/null
;;
ubuntu | debian | raspbian)
DEBIAN_FRONTEND=noninteractive apt-get update -y >/dev/null

View File

@@ -101,11 +101,11 @@ export const initializeTraefik = async ({
console.log("Waiting for service cleanup...");
await new Promise((resolve) => setTimeout(resolve, 5000));
attempts++;
} catch (e) {
} catch (_e) {
break;
}
}
} catch (err) {
} catch (_err) {
console.log("No existing service to remove");
}
@@ -120,7 +120,7 @@ export const initializeTraefik = async ({
await container.remove({ force: true });
await new Promise((resolve) => setTimeout(resolve, 5000));
} catch (error) {
} catch (_err) {
console.log("No existing container to remove");
}

View File

@@ -0,0 +1,4 @@
export { restorePostgresBackup } from "./postgres";
export { restoreMySqlBackup } from "./mysql";
export { restoreMariadbBackup } from "./mariadb";
export { restoreMongoBackup } from "./mongo";

View File

@@ -0,0 +1,56 @@
import type { Destination } from "@dokploy/server/services/destination";
import type { Mariadb } from "@dokploy/server/services/mariadb";
import { getS3Credentials } from "../backups/utils";
import {
getRemoteServiceContainer,
getServiceContainer,
} from "../docker/utils";
import { execAsync, execAsyncRemote } from "../process/execAsync";
export const restoreMariadbBackup = async (
mariadb: Mariadb,
destination: Destination,
database: string,
backupFile: string,
emit: (log: string) => void,
) => {
try {
const { appName, databasePassword, databaseUser, serverId } = mariadb;
const rcloneFlags = getS3Credentials(destination);
const bucketPath = `:s3:${destination.bucket}`;
const backupPath = `${bucketPath}/${backupFile}`;
const { Id: containerName } = serverId
? await getRemoteServiceContainer(serverId, appName)
: await getServiceContainer(appName);
const restoreCommand = `
rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip | docker exec -i ${containerName} mariadb -u ${databaseUser} -p${databasePassword} ${database}
`;
emit("Starting restore...");
emit(`Executing command: ${restoreCommand}`);
if (serverId) {
await execAsyncRemote(serverId, restoreCommand);
} else {
await execAsync(restoreCommand);
}
emit("Restore completed successfully!");
} catch (error) {
console.error(error);
emit(
`Error: ${
error instanceof Error
? error.message
: "Error restoring mariadb backup"
}`,
);
throw new Error(
error instanceof Error ? error.message : "Error restoring mariadb backup",
);
}
};

View File

@@ -0,0 +1,64 @@
import type { Destination } from "@dokploy/server/services/destination";
import type { Mongo } from "@dokploy/server/services/mongo";
import { getS3Credentials } from "../backups/utils";
import {
getRemoteServiceContainer,
getServiceContainer,
} from "../docker/utils";
import { execAsync, execAsyncRemote } from "../process/execAsync";
export const restoreMongoBackup = async (
mongo: Mongo,
destination: Destination,
database: string,
backupFile: string,
emit: (log: string) => void,
) => {
try {
const { appName, databasePassword, databaseUser, serverId } = mongo;
const rcloneFlags = getS3Credentials(destination);
const bucketPath = `:s3:${destination.bucket}`;
const backupPath = `${bucketPath}/${backupFile}`;
const { Id: containerName } = serverId
? await getRemoteServiceContainer(serverId, appName)
: await getServiceContainer(appName);
// For MongoDB, we need to first download the backup file since mongorestore expects a directory
const tempDir = "/tmp/dokploy-restore";
const fileName = backupFile.split("/").pop() || "backup.dump.gz";
const decompressedName = fileName.replace(".gz", "");
const downloadCommand = `\
rm -rf ${tempDir} && \
mkdir -p ${tempDir} && \
rclone copy ${rcloneFlags.join(" ")} "${backupPath}" ${tempDir} && \
cd ${tempDir} && \
gunzip -f "${fileName}" && \
docker exec -i ${containerName} mongorestore --username ${databaseUser} --password ${databasePassword} --authenticationDatabase admin --db ${database} --archive < "${decompressedName}" && \
rm -rf ${tempDir}`;
emit("Starting restore...");
emit(`Executing command: ${downloadCommand}`);
if (serverId) {
await execAsyncRemote(serverId, downloadCommand);
} else {
await execAsync(downloadCommand);
}
emit("Restore completed successfully!");
} catch (error) {
console.error(error);
emit(
`Error: ${
error instanceof Error ? error.message : "Error restoring mongo backup"
}`,
);
throw new Error(
error instanceof Error ? error.message : "Error restoring mongo backup",
);
}
};

View File

@@ -0,0 +1,54 @@
import type { Destination } from "@dokploy/server/services/destination";
import type { MySql } from "@dokploy/server/services/mysql";
import { getS3Credentials } from "../backups/utils";
import {
getRemoteServiceContainer,
getServiceContainer,
} from "../docker/utils";
import { execAsync, execAsyncRemote } from "../process/execAsync";
export const restoreMySqlBackup = async (
mysql: MySql,
destination: Destination,
database: string,
backupFile: string,
emit: (log: string) => void,
) => {
try {
const { appName, databaseRootPassword, serverId } = mysql;
const rcloneFlags = getS3Credentials(destination);
const bucketPath = `:s3:${destination.bucket}`;
const backupPath = `${bucketPath}/${backupFile}`;
const { Id: containerName } = serverId
? await getRemoteServiceContainer(serverId, appName)
: await getServiceContainer(appName);
const restoreCommand = `
rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip | docker exec -i ${containerName} mysql -u root -p${databaseRootPassword} ${database}
`;
emit("Starting restore...");
emit(`Executing command: ${restoreCommand}`);
if (serverId) {
await execAsyncRemote(serverId, restoreCommand);
} else {
await execAsync(restoreCommand);
}
emit("Restore completed successfully!");
} catch (error) {
console.error(error);
emit(
`Error: ${
error instanceof Error ? error.message : "Error restoring mysql backup"
}`,
);
throw new Error(
error instanceof Error ? error.message : "Error restoring mysql backup",
);
}
};

View File

@@ -0,0 +1,60 @@
import type { Destination } from "@dokploy/server/services/destination";
import type { Postgres } from "@dokploy/server/services/postgres";
import { getS3Credentials } from "../backups/utils";
import {
getRemoteServiceContainer,
getServiceContainer,
} from "../docker/utils";
import { execAsync, execAsyncRemote } from "../process/execAsync";
export const restorePostgresBackup = async (
postgres: Postgres,
destination: Destination,
database: string,
backupFile: string,
emit: (log: string) => void,
) => {
try {
const { appName, databaseUser, serverId } = postgres;
const rcloneFlags = getS3Credentials(destination);
const bucketPath = `:s3:${destination.bucket}`;
const backupPath = `${bucketPath}/${backupFile}`;
const { Id: containerName } = serverId
? await getRemoteServiceContainer(serverId, appName)
: await getServiceContainer(appName);
emit("Starting restore...");
emit(`Backup path: ${backupPath}`);
const command = `\
rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip | docker exec -i ${containerName} pg_restore -U ${databaseUser} -d ${database} --clean --if-exists`;
emit(`Executing command: ${command}`);
if (serverId) {
const { stdout, stderr } = await execAsyncRemote(serverId, command);
emit(stdout);
emit(stderr);
} else {
const { stdout, stderr } = await execAsync(command);
console.log("stdout", stdout);
console.log("stderr", stderr);
emit(stdout);
emit(stderr);
}
emit("Restore completed successfully!");
} catch (error) {
emit(
`Error: ${
error instanceof Error
? error.message
: "Error restoring postgres backup"
}`,
);
throw error;
}
};