mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
Merge pull request #3718 from Dokploy/feat/remove-internationalization
chore(dependencies): update zod version across multiple packages to 3…
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"redis": "4.7.0",
|
||||
"zod": "^3.25.32"
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.16.0",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Loader2, Palette, User } from "lucide-react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -73,7 +72,6 @@ export const ProfileForm = () => {
|
||||
isError,
|
||||
error,
|
||||
} = api.user.update.useMutation();
|
||||
const { t } = useTranslation("settings");
|
||||
const [gravatarHash, setGravatarHash] = useState<string | null>(null);
|
||||
const colorInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -157,10 +155,10 @@ export const ProfileForm = () => {
|
||||
<div>
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
<User className="size-6 text-muted-foreground self-center" />
|
||||
{t("settings.profile.title")}
|
||||
Account
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t("settings.profile.description")}
|
||||
Change the details of your profile here.
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
@@ -213,12 +211,9 @@ export const ProfileForm = () => {
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("settings.profile.email")}</FormLabel>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t("settings.profile.email")}
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder="Email" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -233,7 +228,7 @@ export const ProfileForm = () => {
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder={t("settings.profile.password")}
|
||||
placeholder="Current Password"
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
/>
|
||||
@@ -247,13 +242,11 @@ export const ProfileForm = () => {
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("settings.profile.password")}
|
||||
</FormLabel>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder={t("settings.profile.password")}
|
||||
placeholder="Password"
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
/>
|
||||
@@ -268,9 +261,7 @@ export const ProfileForm = () => {
|
||||
name="image"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("settings.profile.avatar")}
|
||||
</FormLabel>
|
||||
<FormLabel>Avatar</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={(e) => {
|
||||
@@ -454,7 +445,7 @@ export const ProfileForm = () => {
|
||||
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button type="submit" isLoading={isUpdating}>
|
||||
{t("settings.common.save")}
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { UpdateServerIp } from "@/components/dashboard/settings/web-server/update-server-ip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -17,7 +16,6 @@ import { TerminalModal } from "../../web-server/terminal-modal";
|
||||
import { GPUSupportModal } from "../gpu-support-modal";
|
||||
|
||||
export const ShowDokployActions = () => {
|
||||
const { t } = useTranslation("settings");
|
||||
const { mutateAsync: reloadServer, isLoading } =
|
||||
api.settings.reloadServer.useMutation();
|
||||
|
||||
@@ -30,13 +28,11 @@ export const ShowDokployActions = () => {
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild disabled={isLoading}>
|
||||
<Button isLoading={isLoading} variant="outline">
|
||||
{t("settings.server.webServer.server.label")}
|
||||
Server
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="start">
|
||||
<DropdownMenuLabel>
|
||||
{t("settings.server.webServer.actions")}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
@@ -51,17 +47,17 @@ export const ShowDokployActions = () => {
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span>{t("settings.server.webServer.reload")}</span>
|
||||
<span>Reload</span>
|
||||
</DropdownMenuItem>
|
||||
<TerminalModal serverId="local">
|
||||
<span>{t("settings.common.enterTerminal")}</span>
|
||||
<span>Terminal</span>
|
||||
</TerminalModal>
|
||||
<ShowModalLogs appName="dokploy">
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
{t("settings.server.webServer.watchLogs")}
|
||||
View Logs
|
||||
</DropdownMenuItem>
|
||||
</ShowModalLogs>
|
||||
<GPUSupportModal />
|
||||
@@ -70,7 +66,7 @@ export const ShowDokployActions = () => {
|
||||
className="cursor-pointer"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
{t("settings.server.webServer.updateServerIp")}
|
||||
Update Server IP
|
||||
</DropdownMenuItem>
|
||||
</UpdateServerIp>
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -16,7 +15,6 @@ interface Props {
|
||||
serverId?: string;
|
||||
}
|
||||
export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
const { t } = useTranslation("settings");
|
||||
const { mutateAsync: cleanAll, isLoading: cleanAllIsLoading } =
|
||||
api.settings.cleanAll.useMutation();
|
||||
|
||||
@@ -64,13 +62,11 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
}
|
||||
variant="outline"
|
||||
>
|
||||
{t("settings.server.webServer.storage.label")}
|
||||
Space
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-64" align="start">
|
||||
<DropdownMenuLabel>
|
||||
{t("settings.server.webServer.actions")}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
@@ -87,9 +83,7 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{t("settings.server.webServer.storage.cleanUnusedImages")}
|
||||
</span>
|
||||
<span>Clean unused images</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer"
|
||||
@@ -105,9 +99,7 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{t("settings.server.webServer.storage.cleanUnusedVolumes")}
|
||||
</span>
|
||||
<span>Clean unused volumes</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
@@ -124,9 +116,7 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{t("settings.server.webServer.storage.cleanStoppedContainers")}
|
||||
</span>
|
||||
<span>Clean stopped containers</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
@@ -143,9 +133,7 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{t("settings.server.webServer.storage.cleanDockerBuilder")}
|
||||
</span>
|
||||
<span>Clean Docker Builder & System</span>
|
||||
</DropdownMenuItem>
|
||||
{!serverId && (
|
||||
<DropdownMenuItem
|
||||
@@ -160,9 +148,7 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{t("settings.server.webServer.storage.cleanMonitoring")}
|
||||
</span>
|
||||
<span>Clean Monitoring</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
@@ -180,7 +166,7 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>{t("settings.server.webServer.storage.cleanAll")}</span>
|
||||
<span>Clean all</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
@@ -22,7 +21,6 @@ interface Props {
|
||||
serverId?: string;
|
||||
}
|
||||
export const ShowTraefikActions = ({ serverId }: Props) => {
|
||||
const { t } = useTranslation("settings");
|
||||
const { mutateAsync: reloadTraefik, isLoading: reloadTraefikIsLoading } =
|
||||
api.settings.reloadTraefik.useMutation();
|
||||
|
||||
@@ -75,13 +73,11 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
|
||||
}
|
||||
variant="outline"
|
||||
>
|
||||
{t("settings.server.webServer.traefik.label")}
|
||||
Traefik
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="start">
|
||||
<DropdownMenuLabel>
|
||||
{t("settings.server.webServer.actions")}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
@@ -100,7 +96,7 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
|
||||
className="cursor-pointer"
|
||||
disabled={isReloadHealthCheckExecuting}
|
||||
>
|
||||
<span>{t("settings.server.webServer.reload")}</span>
|
||||
<span>Reload</span>
|
||||
</DropdownMenuItem>
|
||||
<ShowModalLogs
|
||||
appName="dokploy-traefik"
|
||||
@@ -111,7 +107,7 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{t("settings.server.webServer.watchLogs")}
|
||||
View Logs
|
||||
</DropdownMenuItem>
|
||||
</ShowModalLogs>
|
||||
<EditTraefikEnv serverId={serverId}>
|
||||
@@ -119,7 +115,7 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span>{t("settings.server.webServer.traefik.modifyEnv")}</span>
|
||||
<span>Modify Environment</span>
|
||||
</DropdownMenuItem>
|
||||
</EditTraefikEnv>
|
||||
|
||||
@@ -176,7 +172,7 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span>{t("settings.server.webServer.traefik.managePorts")}</span>
|
||||
<span>Additional Port Mappings</span>
|
||||
</DropdownMenuItem>
|
||||
</ManageTraefikPorts>
|
||||
</DropdownMenuGroup>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Pencil, PlusIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -63,8 +62,6 @@ interface Props {
|
||||
}
|
||||
|
||||
export const HandleServers = ({ serverId, asButton = false }: Props) => {
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
const utils = api.useUtils();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { data: canCreateMoreServers, refetch } =
|
||||
@@ -365,7 +362,7 @@ export const HandleServers = ({ serverId, asButton = false }: Props) => {
|
||||
name="ipAddress"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("settings.terminal.ipAddress")}</FormLabel>
|
||||
<FormLabel>IP Address</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="192.168.1.100" {...field} />
|
||||
</FormControl>
|
||||
@@ -379,7 +376,7 @@ export const HandleServers = ({ serverId, asButton = false }: Props) => {
|
||||
name="port"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("settings.terminal.port")}</FormLabel>
|
||||
<FormLabel>Port</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="22"
|
||||
@@ -409,7 +406,7 @@ export const HandleServers = ({ serverId, asButton = false }: Props) => {
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("settings.terminal.username")}</FormLabel>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="root" {...field} />
|
||||
</FormControl>
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
@@ -52,7 +51,6 @@ import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal";
|
||||
import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription";
|
||||
|
||||
export const ShowServers = () => {
|
||||
const { t } = useTranslation("settings");
|
||||
const router = useRouter();
|
||||
const query = router.query;
|
||||
const { data, refetch, isLoading } = api.server.all.useQuery();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { GlobeIcon } from "lucide-react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -66,7 +65,6 @@ const addServerDomain = z
|
||||
type AddServerDomain = z.infer<typeof addServerDomain>;
|
||||
|
||||
export const WebDomain = () => {
|
||||
const { t } = useTranslation("settings");
|
||||
const { data, refetch } = api.settings.getWebServerSettings.useQuery();
|
||||
const { mutateAsync, isLoading } =
|
||||
api.settings.assignDomainServer.useMutation();
|
||||
@@ -119,10 +117,10 @@ export const WebDomain = () => {
|
||||
<div className="flex flex-col gap-1">
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
<GlobeIcon className="size-6 text-muted-foreground self-center" />
|
||||
{t("settings.server.domain.title")}
|
||||
Server Domain
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t("settings.server.domain.description")}
|
||||
Add a domain to your server application.
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -151,9 +149,7 @@ export const WebDomain = () => {
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("settings.server.domain.form.domain")}
|
||||
</FormLabel>
|
||||
<FormLabel>Domain</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-full"
|
||||
@@ -173,9 +169,7 @@ export const WebDomain = () => {
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("settings.server.domain.form.letsEncryptEmail")}
|
||||
</FormLabel>
|
||||
<FormLabel>Let's Encrypt Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-full"
|
||||
@@ -216,32 +210,20 @@ export const WebDomain = () => {
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem className="md:col-span-2">
|
||||
<FormLabel>
|
||||
{t("settings.server.domain.form.certificate.label")}
|
||||
</FormLabel>
|
||||
<FormLabel>Certificate Provider</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"settings.server.domain.form.certificate.placeholder",
|
||||
)}
|
||||
/>
|
||||
<SelectValue placeholder="Select a certificate" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value={"none"}>
|
||||
{t(
|
||||
"settings.server.domain.form.certificateOptions.none",
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value={"none"}>None</SelectItem>
|
||||
<SelectItem value={"letsencrypt"}>
|
||||
{t(
|
||||
"settings.server.domain.form.certificateOptions.letsencrypt",
|
||||
)}
|
||||
Let's Encrypt
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -254,7 +236,7 @@ export const WebDomain = () => {
|
||||
|
||||
<div className="flex w-full justify-end col-span-2">
|
||||
<Button isLoading={isLoading} type="submit">
|
||||
{t("settings.common.save")}
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ServerIcon } from "lucide-react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -15,7 +14,6 @@ import { ToggleDockerCleanup } from "./servers/actions/toggle-docker-cleanup";
|
||||
import { UpdateServer } from "./web-server/update-server";
|
||||
|
||||
export const WebServer = () => {
|
||||
const { t } = useTranslation("settings");
|
||||
const { data: webServerSettings } =
|
||||
api.settings.getWebServerSettings.useQuery();
|
||||
|
||||
@@ -29,18 +27,16 @@ export const WebServer = () => {
|
||||
<CardHeader className="">
|
||||
<CardTitle className="text-xl flex flex-row gap-2">
|
||||
<ServerIcon className="size-6 text-muted-foreground self-center" />
|
||||
{t("settings.server.webServer.title")}
|
||||
Web Server
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t("settings.server.webServer.description")}
|
||||
</CardDescription>
|
||||
<CardDescription>Reload or clean the web server.</CardDescription>
|
||||
</CardHeader>
|
||||
{/* <CardHeader>
|
||||
<CardTitle className="text-xl">
|
||||
{t("settings.server.webServer.title")}
|
||||
Web Server
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t("settings.server.webServer.description")}
|
||||
Reload or clean the web server.
|
||||
</CardDescription>
|
||||
</CardHeader> */}
|
||||
<CardContent className="space-y-6 py-6 border-t">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Settings } from "lucide-react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
@@ -52,8 +51,6 @@ interface Props {
|
||||
}
|
||||
|
||||
const LocalServerConfig = ({ onSave }: Props) => {
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: getLocalServerData(),
|
||||
resolver: zodResolver(Schema),
|
||||
@@ -77,9 +74,7 @@ const LocalServerConfig = ({ onSave }: Props) => {
|
||||
<div className="flex flex-row items-center gap-2 justify-between w-full">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<Settings className="h-4 w-4" />
|
||||
<span className="dark:hover:text-white">
|
||||
{t("settings.terminal.connectionSettings")}
|
||||
</span>
|
||||
<span className="dark:hover:text-white">Connection settings</span>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
@@ -96,7 +91,7 @@ const LocalServerConfig = ({ onSave }: Props) => {
|
||||
name="port"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("settings.terminal.port")}</FormLabel>
|
||||
<FormLabel>Port</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
@@ -124,7 +119,7 @@ const LocalServerConfig = ({ onSave }: Props) => {
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("settings.terminal.username")}</FormLabel>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="root" {...field} />
|
||||
</FormControl>
|
||||
@@ -142,7 +137,7 @@ const LocalServerConfig = ({ onSave }: Props) => {
|
||||
className="ml-auto"
|
||||
disabled={!form.formState.isDirty}
|
||||
>
|
||||
{t("settings.common.save")}
|
||||
Save
|
||||
</Button>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ArrowRightLeft, Plus, Trash2 } from "lucide-react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useHealthCheckAfterMutation } from "@/hooks/use-health-check-after-mutation";
|
||||
import type React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -56,7 +55,6 @@ const TraefikPortsSchema = z.object({
|
||||
type TraefikPortsForm = z.infer<typeof TraefikPortsSchema>;
|
||||
|
||||
export const ManageTraefikPorts = ({ children, serverId }: Props) => {
|
||||
const { t } = useTranslation("settings");
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const form = useForm<TraefikPortsForm>({
|
||||
@@ -84,7 +82,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
|
||||
isExecuting: isHealthCheckExecuting,
|
||||
} = useHealthCheckAfterMutation({
|
||||
initialDelay: 5000,
|
||||
successMessage: t("settings.server.webServer.traefik.portsUpdated"),
|
||||
successMessage: "Ports updated successfully",
|
||||
onSuccess: () => {
|
||||
refetchPorts();
|
||||
setOpen(false);
|
||||
@@ -129,14 +127,12 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
|
||||
<DialogContent className="sm:max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-xl">
|
||||
{t("settings.server.webServer.traefik.managePorts")}
|
||||
Additional Port Mappings
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-base w-full">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col gap-1">
|
||||
{t(
|
||||
"settings.server.webServer.traefik.managePortsDescription",
|
||||
)}
|
||||
Add or remove additional ports for Traefik
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{fields.length} port mapping{fields.length !== 1 ? "s" : ""}{" "}
|
||||
configured
|
||||
@@ -179,9 +175,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-sm font-medium text-muted-foreground">
|
||||
{t(
|
||||
"settings.server.webServer.traefik.targetPort",
|
||||
)}
|
||||
Target Port
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -210,9 +204,7 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-sm font-medium text-muted-foreground">
|
||||
{t(
|
||||
"settings.server.webServer.traefik.publishedPort",
|
||||
)}
|
||||
Published Port
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
|
||||
@@ -10,18 +10,9 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { Languages } from "@/lib/languages";
|
||||
import { getFallbackAvatarInitials } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import useLocale from "@/utils/hooks/use-locale";
|
||||
import { ModeToggle } from "../ui/modeToggle";
|
||||
import { SidebarMenuButton } from "../ui/sidebar";
|
||||
|
||||
@@ -32,7 +23,6 @@ export const UserNav = () => {
|
||||
const { data } = api.user.get.useQuery();
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
|
||||
const { locale, setLocale } = useLocale();
|
||||
// const { mutateAsync } = api.auth.logout.useMutation();
|
||||
|
||||
return (
|
||||
@@ -155,39 +145,19 @@ export const UserNav = () => {
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<div className="flex items-center justify-between px-2 py-1.5">
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={async () => {
|
||||
await authClient.signOut().then(() => {
|
||||
router.push("/");
|
||||
});
|
||||
// await mutateAsync().then(() => {
|
||||
// router.push("/");
|
||||
// });
|
||||
}}
|
||||
>
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
<div className="w-32">
|
||||
<Select
|
||||
onValueChange={setLocale}
|
||||
defaultValue={locale}
|
||||
value={locale}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select Language" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.values(Languages).map((language) => (
|
||||
<SelectItem key={language.code} value={language.code}>
|
||||
{language.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={async () => {
|
||||
await authClient.signOut().then(() => {
|
||||
router.push("/");
|
||||
});
|
||||
// await mutateAsync().then(() => {
|
||||
// router.push("/");
|
||||
// });
|
||||
}}
|
||||
>
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* Sorted list based off of population of the country / speakers of the language.
|
||||
*/
|
||||
export const Languages = {
|
||||
english: { code: "en", name: "English" },
|
||||
spanish: { code: "es", name: "Español" },
|
||||
chineseSimplified: { code: "zh-Hans", name: "简体中文" },
|
||||
chineseTraditional: { code: "zh-Hant", name: "繁體中文" },
|
||||
portuguese: { code: "pt-br", name: "Português" },
|
||||
russian: { code: "ru", name: "Русский" },
|
||||
japanese: { code: "ja", name: "日本語" },
|
||||
german: { code: "de", name: "Deutsch" },
|
||||
korean: { code: "ko", name: "한국어" },
|
||||
french: { code: "fr", name: "Français" },
|
||||
turkish: { code: "tr", name: "Türkçe" },
|
||||
italian: { code: "it", name: "Italiano" },
|
||||
polish: { code: "pl", name: "Polski" },
|
||||
ukrainian: { code: "uk", name: "Українська" },
|
||||
persian: { code: "fa", name: "فارسی" },
|
||||
dutch: { code: "nl", name: "Nederlands" },
|
||||
indonesian: { code: "id", name: "Bahasa Indonesia" },
|
||||
kazakh: { code: "kz", name: "Қазақ" },
|
||||
norwegian: { code: "no", name: "Norsk" },
|
||||
azerbaijani: { code: "az", name: "Azərbaycan" },
|
||||
malayalam: { code: "ml", name: "മലയാളം" },
|
||||
};
|
||||
|
||||
export type Language = keyof typeof Languages;
|
||||
export type LanguageCode = (typeof Languages)[keyof typeof Languages]["code"];
|
||||
@@ -10,15 +10,6 @@ const nextConfig = {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
transpilePackages: ["@dokploy/server"],
|
||||
/**
|
||||
* If you are using `appDir` then you must comment the below `i18n` config out.
|
||||
*
|
||||
* @see https://github.com/vercel/next.js/issues/41980
|
||||
*/
|
||||
i18n: {
|
||||
locales: ["en"],
|
||||
defaultLocale: "en",
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"build": "npm run build-server && npm run build-next",
|
||||
"start": "node -r dotenv/config dist/migration.mjs && node -r dotenv/config dist/server.mjs",
|
||||
"build-server": "tsx esbuild.config.ts",
|
||||
"build-next": "next build --webpack",
|
||||
"build-next": "next build",
|
||||
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
|
||||
"wait-for-postgres": "node -r dotenv/config dist/wait-for-postgres.mjs",
|
||||
"wait-for-postgres-dev": "tsx -r dotenv/config wait-for-postgres.ts",
|
||||
@@ -41,13 +41,13 @@
|
||||
"dependencies": {
|
||||
"resend": "^6.0.2",
|
||||
"@better-auth/sso": "1.4.18",
|
||||
"@ai-sdk/anthropic": "^2.0.5",
|
||||
"@ai-sdk/azure": "^2.0.16",
|
||||
"@ai-sdk/cohere": "^2.0.4",
|
||||
"@ai-sdk/deepinfra": "^1.0.10",
|
||||
"@ai-sdk/mistral": "^2.0.7",
|
||||
"@ai-sdk/openai": "^2.0.16",
|
||||
"@ai-sdk/openai-compatible": "^1.0.10",
|
||||
"@ai-sdk/anthropic": "^3.0.44",
|
||||
"@ai-sdk/azure": "^3.0.30",
|
||||
"@ai-sdk/cohere": "^3.0.21",
|
||||
"@ai-sdk/deepinfra": "^2.0.34",
|
||||
"@ai-sdk/mistral": "^3.0.20",
|
||||
"@ai-sdk/openai": "^3.0.29",
|
||||
"@ai-sdk/openai-compatible": "^2.0.30",
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
@@ -95,8 +95,8 @@
|
||||
"@xterm/addon-clipboard": "0.1.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"ai": "^5.0.17",
|
||||
"ai-sdk-ollama": "^0.5.1",
|
||||
"ai": "^6.0.86",
|
||||
"ai-sdk-ollama": "^3.7.0",
|
||||
"bcrypt": "5.1.1",
|
||||
"better-auth": "1.4.18",
|
||||
"bl": "6.0.11",
|
||||
@@ -113,7 +113,6 @@
|
||||
"drizzle-orm": "^0.41.0",
|
||||
"drizzle-zod": "0.5.1",
|
||||
"fancy-ansi": "^0.1.3",
|
||||
"i18next": "^23.16.8",
|
||||
"input-otp": "^1.4.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lodash": "4.17.21",
|
||||
@@ -121,7 +120,6 @@
|
||||
"micromatch": "4.0.8",
|
||||
"nanoid": "3.3.11",
|
||||
"next": "^16.1.6",
|
||||
"next-i18next": "^15.4.2",
|
||||
"next-themes": "^0.2.1",
|
||||
"nextjs-toploader": "^3.9.17",
|
||||
"node-os-utils": "2.0.1",
|
||||
@@ -139,7 +137,6 @@
|
||||
"react-day-picker": "8.10.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.56.4",
|
||||
"react-i18next": "^15.5.2",
|
||||
"react-markdown": "^9.1.0",
|
||||
"recharts": "^2.15.3",
|
||||
"slugify": "^1.6.6",
|
||||
@@ -147,7 +144,7 @@
|
||||
"ssh2": "1.15.0",
|
||||
"stripe": "17.2.0",
|
||||
"superjson": "^2.2.2",
|
||||
"swagger-ui-react": "^5.22.0",
|
||||
"swagger-ui-react": "^5.31.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"toml": "3.0.0",
|
||||
@@ -156,7 +153,7 @@
|
||||
"ws": "8.16.0",
|
||||
"xterm-addon-fit": "^0.8.0",
|
||||
"yaml": "2.8.1",
|
||||
"zod": "^3.25.32",
|
||||
"zod": "^3.25.76",
|
||||
"zod-form-data": "^2.0.7",
|
||||
"semver": "7.7.3"
|
||||
},
|
||||
|
||||
@@ -4,13 +4,11 @@ import type { NextPage } from "next";
|
||||
import type { AppProps } from "next/app";
|
||||
import { Inter } from "next/font/google";
|
||||
import Head from "next/head";
|
||||
import { appWithTranslation } from "next-i18next";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import NextTopLoader from "nextjs-toploader";
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
import { SearchCommand } from "@/components/dashboard/search-command";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { Languages } from "@/lib/languages";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
@@ -58,14 +56,4 @@ const MyApp = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default api.withTRPC(
|
||||
appWithTranslation(MyApp, {
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: Object.values(Languages).map((language) => language.code),
|
||||
localeDetection: false,
|
||||
},
|
||||
fallbackLng: "en",
|
||||
keySeparator: false,
|
||||
}),
|
||||
);
|
||||
export default api.withTRPC(MyApp);
|
||||
|
||||
@@ -6,7 +6,6 @@ import superjson from "superjson";
|
||||
import { AiForm } from "@/components/dashboard/settings/ai-form";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
@@ -26,7 +25,6 @@ export async function getServerSideProps(
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(req);
|
||||
const locale = getLocale(req.cookies);
|
||||
|
||||
const helpers = createServerSideHelpers({
|
||||
router: appRouter,
|
||||
@@ -55,7 +53,6 @@ export async function getServerSideProps(
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
...(await serverSideTranslations(locale, ["settings"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { LicenseKeySettings } from "@/components/proprietary/license-keys/license-key";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
@@ -35,7 +34,6 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const locale = await getLocale(req.cookies);
|
||||
const { user, session } = await validateRequest(ctx.req);
|
||||
if (!user) {
|
||||
return {
|
||||
@@ -70,7 +68,6 @@ export async function getServerSideProps(
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
...(await serverSideTranslations(locale, ["settings"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { ProfileForm } from "@/components/dashboard/settings/profile/profile-for
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
|
||||
const Page = () => {
|
||||
const { data } = api.user.get.useQuery();
|
||||
@@ -37,7 +36,6 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const locale = getLocale(req.cookies);
|
||||
const { user, session } = await validateRequest(req);
|
||||
|
||||
const helpers = createServerSideHelpers({
|
||||
@@ -67,7 +65,6 @@ export async function getServerSideProps(
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
...(await serverSideTranslations(locale, ["settings"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
|
||||
const Page = () => {
|
||||
const { data: user } = api.user.get.useQuery();
|
||||
@@ -42,7 +41,6 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const locale = await getLocale(req.cookies);
|
||||
if (IS_CLOUD) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -85,7 +83,6 @@ export async function getServerSideProps(
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
...(await serverSideTranslations(locale, ["settings"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import superjson from "superjson";
|
||||
import { ShowServers } from "@/components/dashboard/settings/servers/show-servers";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
@@ -25,7 +24,6 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const locale = await getLocale(req.cookies);
|
||||
const { user, session } = await validateRequest(req);
|
||||
if (!user) {
|
||||
return {
|
||||
@@ -61,7 +59,6 @@ export async function getServerSideProps(
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
...(await serverSideTranslations(locale, ["settings"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { EnterpriseFeatureGate } from "@/components/proprietary/enterprise-featu
|
||||
import { SSOSettings } from "@/components/proprietary/sso/sso-settings";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
@@ -43,7 +42,6 @@ Page.getLayout = (page: ReactElement) => {
|
||||
|
||||
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||
const { req, res } = ctx;
|
||||
const locale = await getLocale(req.cookies);
|
||||
const { user, session } = await validateRequest(ctx.req);
|
||||
if (!user) {
|
||||
return {
|
||||
@@ -78,7 +76,6 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
...(await serverSideTranslations(locale, ["settings"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
"**/*.js",
|
||||
".next/types/**/*.ts",
|
||||
"env.js",
|
||||
"next.config.mjs",
|
||||
"next-i18next.config.mjs"
|
||||
"next.config.mjs"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import Cookies from "js-cookie";
|
||||
import type { LanguageCode } from "@/lib/languages";
|
||||
|
||||
export default function useLocale() {
|
||||
const currentLocale = (Cookies.get("DOKPLOY_LOCALE") ?? "en") as LanguageCode;
|
||||
|
||||
const setLocale = (locale: LanguageCode) => {
|
||||
Cookies.set("DOKPLOY_LOCALE", locale, { expires: 365 });
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return {
|
||||
locale: currentLocale,
|
||||
setLocale,
|
||||
};
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import type { NextApiRequestCookies } from "next/dist/server/api-utils";
|
||||
|
||||
export function getLocale(cookies: NextApiRequestCookies) {
|
||||
const locale = cookies.DOKPLOY_LOCALE ?? "en";
|
||||
return locale;
|
||||
}
|
||||
|
||||
import { serverSideTranslations as originalServerSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import { Languages } from "@/lib/languages";
|
||||
|
||||
export const serverSideTranslations = (
|
||||
locale: string,
|
||||
namespaces = ["common"],
|
||||
) =>
|
||||
originalServerSideTranslations(locale, namespaces, {
|
||||
fallbackLng: "en",
|
||||
keySeparator: false,
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: Object.values(Languages).map((language) => language.code),
|
||||
localeDetection: false,
|
||||
},
|
||||
});
|
||||
@@ -20,7 +20,7 @@
|
||||
"pino-pretty": "11.2.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"zod": "^3.25.32"
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.16.0",
|
||||
|
||||
27
packages/server/DEBUG-BUILD.md
Normal file
27
packages/server/DEBUG-BUILD.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Debug build OOM – orden para probar
|
||||
|
||||
Ejecuta desde `packages/server` (o `pnpm --filter=@dokploy/server run <script>` desde la raíz).
|
||||
|
||||
1. **`pnpm run build:debug:noEmit`**
|
||||
Solo typecheck, no escribe archivos.
|
||||
- Si hace **OOM** → el problema es el análisis de tipos (ej. zod u otras libs).
|
||||
- Si **pasa** → el problema está en emit (JS o `.d.ts`).
|
||||
|
||||
2. **`pnpm run build:debug:noEmit:8gb`**
|
||||
Mismo que el anterior pero con 8GB de heap.
|
||||
- Si con 8GB **pasa** y sin 8GB **no** → el typecheck necesita más memoria.
|
||||
|
||||
3. **`pnpm run build:debug:noDecl`**
|
||||
Compila solo JS (sin `declaration`).
|
||||
- Si hace **OOM** → el problema es emitir JS.
|
||||
- Si **pasa** → el problema es generar `.d.ts`.
|
||||
|
||||
4. **`pnpm run build:debug:declOnly`**
|
||||
Solo genera declaraciones (`.d.ts`).
|
||||
- Si hace **OOM** → el cuello de botella son las declaraciones.
|
||||
|
||||
5. **`pnpm run build:debug:full`**
|
||||
Build completo con `--extendedDiagnostics` (imprime estadísticas al final).
|
||||
- Para ver en qué paso se va la memoria si no has localizado antes.
|
||||
|
||||
Con eso sabes si el OOM viene de: typecheck, emit JS o emit declarations, y puedes elegir fix (más memoria, esbuild para JS, o no emitir declarations).
|
||||
@@ -30,13 +30,13 @@
|
||||
"generate:drizzle": "pnpm dlx @better-auth/cli generate --output auth-schema2.ts --config src/lib/auth.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.5",
|
||||
"@ai-sdk/azure": "^2.0.16",
|
||||
"@ai-sdk/cohere": "^2.0.4",
|
||||
"@ai-sdk/deepinfra": "^1.0.10",
|
||||
"@ai-sdk/mistral": "^2.0.7",
|
||||
"@ai-sdk/openai": "^2.0.16",
|
||||
"@ai-sdk/openai-compatible": "^1.0.10",
|
||||
"@ai-sdk/anthropic": "^3.0.44",
|
||||
"@ai-sdk/azure": "^3.0.30",
|
||||
"@ai-sdk/cohere": "^3.0.21",
|
||||
"@ai-sdk/deepinfra": "^2.0.34",
|
||||
"@ai-sdk/mistral": "^3.0.20",
|
||||
"@ai-sdk/openai": "^3.0.29",
|
||||
"@ai-sdk/openai-compatible": "^2.0.30",
|
||||
"@better-auth/utils": "0.3.0",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@octokit/auth-app": "^6.1.3",
|
||||
@@ -44,11 +44,11 @@
|
||||
"@oslojs/crypto": "1.0.1",
|
||||
"@oslojs/encoding": "1.1.0",
|
||||
"@react-email/components": "^0.0.21",
|
||||
"@better-auth/sso":"1.4.18",
|
||||
"@better-auth/sso": "1.4.18",
|
||||
"@trpc/server": "^10.45.2",
|
||||
"adm-zip": "^0.5.16",
|
||||
"ai": "^5.0.17",
|
||||
"ai-sdk-ollama": "^0.5.1",
|
||||
"ai": "^6.0.86",
|
||||
"ai-sdk-ollama": "^3.7.0",
|
||||
"bcrypt": "5.1.1",
|
||||
"better-auth": "1.4.18",
|
||||
"bl": "6.0.11",
|
||||
@@ -81,11 +81,11 @@
|
||||
"ssh2": "1.15.0",
|
||||
"toml": "3.0.0",
|
||||
"ws": "8.16.0",
|
||||
"zod": "^3.25.32",
|
||||
"zod": "^3.25.76",
|
||||
"semver": "7.7.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@better-auth/cli": "1.4.18",
|
||||
"@better-auth/cli": "1.4.18",
|
||||
"@types/semver": "7.7.1",
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
@@ -115,4 +115,4 @@
|
||||
"node": "^20.16.0",
|
||||
"pnpm": ">=9.12.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,31 @@ import { db } from "@dokploy/server/db";
|
||||
import { ai } from "@dokploy/server/db/schema";
|
||||
import { selectAIProvider } from "@dokploy/server/utils/ai/select-ai-provider";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { generateObject } from "ai";
|
||||
import { generateText, Output } from "ai";
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { IS_CLOUD } from "../constants";
|
||||
import { findServerById } from "./server";
|
||||
import { getWebServerSettings } from "./web-server-settings";
|
||||
|
||||
interface SuggestionItem {
|
||||
id: string;
|
||||
name: string;
|
||||
shortDescription: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface SuggestionsOutput {
|
||||
suggestions: SuggestionItem[];
|
||||
}
|
||||
|
||||
interface DockerOutput {
|
||||
dockerCompose: string;
|
||||
envVariables: Array<{ name: string; value: string }>;
|
||||
domains: Array<{ host: string; port: number; serviceName: string }>;
|
||||
configFiles?: Array<{ content: string; filePath: string }>;
|
||||
}
|
||||
|
||||
export const getAiSettingsByOrganizationId = async (organizationId: string) => {
|
||||
const aiSettings = await db.query.ai.findMany({
|
||||
where: eq(ai.organizationId, organizationId),
|
||||
@@ -60,7 +78,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const suggestVariants = async ({
|
||||
organizationId,
|
||||
organizationId: _organizationId,
|
||||
aiId,
|
||||
input,
|
||||
serverId,
|
||||
@@ -90,173 +108,177 @@ export const suggestVariants = async ({
|
||||
ip = "127.0.0.1";
|
||||
}
|
||||
|
||||
const { object } = await generateObject({
|
||||
model,
|
||||
output: "object",
|
||||
schema: z.object({
|
||||
suggestions: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
shortDescription: z.string(),
|
||||
description: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
prompt: `
|
||||
Act as advanced DevOps engineer and analyze the user's request to determine the appropriate suggestions (up to 3 items).
|
||||
|
||||
CRITICAL - Read the user's request carefully and follow the appropriate strategy:
|
||||
|
||||
Strategy A - If the user specifies a PARTICULAR APPLICATION/SERVICE (e.g., "deploy Chatwoot", "install sendingtk/chatwoot:develop", "setup Bitwarden"):
|
||||
- Generate different deployment VARIANTS of that SAME application
|
||||
- Each variant should be a different configuration (minimal, full stack, with different databases, development vs production, etc.)
|
||||
- Example: For "Chatwoot" → "Chatwoot with PostgreSQL", "Chatwoot Development", "Chatwoot Full Stack"
|
||||
- The name MUST include the specific application name the user mentioned
|
||||
|
||||
Strategy B - If the user describes a GENERAL NEED or USE CASE (e.g., "personal blog", "project management tool", "chat application"):
|
||||
- Suggest different open source projects that fulfill that need
|
||||
- Each suggestion should be a different tool/platform that solves the same problem
|
||||
- Example: For "personal blog" → "WordPress", "Ghost", "Hugo with Nginx"
|
||||
- The name should be the actual project name
|
||||
|
||||
Return your response as a JSON object with the following structure:
|
||||
{
|
||||
"suggestions": [
|
||||
{
|
||||
"id": "project-or-variant-slug",
|
||||
"name": "Project Name or Variant Name",
|
||||
"shortDescription": "Brief one-line description",
|
||||
"description": "Detailed description"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Important rules for the response:
|
||||
1. Use slug format for the id field (lowercase, hyphenated)
|
||||
2. Determine which strategy to use based on whether the user specified a particular application or described a general need
|
||||
3. For Strategy A (specific app): The name must include the app name and describe the variant configuration
|
||||
4. For Strategy B (general need): The name should be the actual project/tool name that fulfills the need
|
||||
5. The description field should ONLY contain a plain text description of the project or variant, its features, and use cases
|
||||
6. Do NOT include any code snippets, configuration examples, or installation instructions in the description
|
||||
7. The shortDescription should be a single-line summary focusing on key technologies or differentiators
|
||||
8. All suggestions should be installable in docker and have docker compose support
|
||||
9. Provide variety in your suggestions - different complexity levels, tech stacks, or approaches
|
||||
|
||||
User wants to create a new project with the following details:
|
||||
|
||||
${input}
|
||||
`,
|
||||
const suggestionsSchema = z.object({
|
||||
suggestions: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
shortDescription: z.string(),
|
||||
description: z.string(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
const suggestionsResult = await generateText({
|
||||
model,
|
||||
// @ts-ignore - Zod + AI SDK Output.object() causes excessively deep instantiation
|
||||
output: Output.object({ schema: suggestionsSchema }),
|
||||
prompt: `
|
||||
Act as advanced DevOps engineer and analyze the user's request to determine the appropriate suggestions (up to 3 items).
|
||||
|
||||
CRITICAL - Read the user's request carefully and follow the appropriate strategy:
|
||||
|
||||
Strategy A - If the user specifies a PARTICULAR APPLICATION/SERVICE (e.g., "deploy Chatwoot", "install sendingtk/chatwoot:develop", "setup Bitwarden"):
|
||||
- Generate different deployment VARIANTS of that SAME application
|
||||
- Each variant should be a different configuration (minimal, full stack, with different databases, development vs production, etc.)
|
||||
- Example: For "Chatwoot" → "Chatwoot with PostgreSQL", "Chatwoot Development", "Chatwoot Full Stack"
|
||||
- The name MUST include the specific application name the user mentioned
|
||||
|
||||
Strategy B - If the user describes a GENERAL NEED or USE CASE (e.g., "personal blog", "project management tool", "chat application"):
|
||||
- Suggest different open source projects that fulfill that need
|
||||
- Each suggestion should be a different tool/platform that solves the same problem
|
||||
- Example: For "personal blog" → "WordPress", "Ghost", "Hugo with Nginx"
|
||||
- The name should be the actual project name
|
||||
|
||||
Return your response as a JSON object with the following structure:
|
||||
{
|
||||
"suggestions": [
|
||||
{
|
||||
"id": "project-or-variant-slug",
|
||||
"name": "Project Name or Variant Name",
|
||||
"shortDescription": "Brief one-line description",
|
||||
"description": "Detailed description"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Important rules for the response:
|
||||
1. Use slug format for the id field (lowercase, hyphenated)
|
||||
2. Determine which strategy to use based on whether the user specified a particular application or described a general need
|
||||
3. For Strategy A (specific app): The name must include the app name and describe the variant configuration
|
||||
4. For Strategy B (general need): The name should be the actual project/tool name that fulfills the need
|
||||
5. The description field should ONLY contain a plain text description of the project or variant, its features, and use cases
|
||||
6. Do NOT include any code snippets, configuration examples, or installation instructions in the description
|
||||
7. The shortDescription should be a single-line summary focusing on key technologies or differentiators
|
||||
8. All suggestions should be installable in docker and have docker compose support
|
||||
9. Provide variety in your suggestions - different complexity levels, tech stacks, or approaches
|
||||
|
||||
User wants to create a new project with the following details:
|
||||
|
||||
${input}
|
||||
`,
|
||||
});
|
||||
const object = suggestionsResult.output as SuggestionsOutput | undefined;
|
||||
|
||||
if (object?.suggestions?.length) {
|
||||
const dockerSchema = z.object({
|
||||
dockerCompose: z.string(),
|
||||
envVariables: z.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
value: z.string(),
|
||||
}),
|
||||
),
|
||||
domains: z.array(
|
||||
z.object({
|
||||
host: z.string(),
|
||||
port: z.number(),
|
||||
serviceName: z.string(),
|
||||
}),
|
||||
),
|
||||
configFiles: z
|
||||
.array(
|
||||
z.object({
|
||||
content: z.string(),
|
||||
filePath: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
const result = [];
|
||||
for (const suggestion of object.suggestions) {
|
||||
try {
|
||||
const { object: docker } = await generateObject({
|
||||
const dockerResult = await generateText({
|
||||
model,
|
||||
output: "object",
|
||||
schema: z.object({
|
||||
dockerCompose: z.string(),
|
||||
envVariables: z.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
value: z.string(),
|
||||
}),
|
||||
),
|
||||
domains: z.array(
|
||||
z.object({
|
||||
host: z.string(),
|
||||
port: z.number(),
|
||||
serviceName: z.string(),
|
||||
}),
|
||||
),
|
||||
configFiles: z
|
||||
.array(
|
||||
z.object({
|
||||
content: z.string(),
|
||||
filePath: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
// @ts-ignore - Zod + AI SDK Output.object() causes excessively deep instantiation
|
||||
output: Output.object({ schema: dockerSchema }),
|
||||
prompt: `
|
||||
Act as advanced DevOps engineer and generate docker compose with environment variables and domain configurations needed to install the following project.
|
||||
|
||||
Return your response as a JSON object with this structure:
|
||||
{
|
||||
"dockerCompose": "yaml string here",
|
||||
"envVariables": [{"name": "VAR_NAME", "value": "example_value"}],
|
||||
"domains": [{"host": "domain.com", "port": 3000, "serviceName": "service"}],
|
||||
"configFiles": [{"content": "file content", "filePath": "path/to/file"}]
|
||||
}
|
||||
|
||||
Note: configFiles is optional - only include it if configuration files are absolutely required.
|
||||
|
||||
Follow these rules:
|
||||
Act as advanced DevOps engineer and generate docker compose with environment variables and domain configurations needed to install the following project.
|
||||
|
||||
Docker Compose Rules:
|
||||
1. Use placeholder like \${VARIABLE_NAME-default} for generated variables in the docker-compose.yml
|
||||
2. Use complex values for passwords/secrets variables
|
||||
3. Don't set container_name field in services
|
||||
4. Don't set version field in the docker compose
|
||||
5. Don't set ports like 'ports: 3000:3000', use 'ports: "3000"' instead
|
||||
6. If a service depends on a database or other service, INCLUDE that service in the docker-compose
|
||||
7. Make sure all required services are defined in the docker-compose
|
||||
Return your response as a JSON object with this structure:
|
||||
{
|
||||
"dockerCompose": "yaml string here",
|
||||
"envVariables": [{"name": "VAR_NAME", "value": "example_value"}],
|
||||
"domains": [{"host": "domain.com", "port": 3000, "serviceName": "service"}],
|
||||
"configFiles": [{"content": "file content", "filePath": "path/to/file"}]
|
||||
}
|
||||
|
||||
Docker Image Rules (CRITICAL):
|
||||
1. ALWAYS use 'image:' field, NEVER use 'build:' field
|
||||
2. NEVER use 'build: .' or any build directive - we don't have local Dockerfiles
|
||||
3. Use images from Docker Hub or other public registries (e.g., docker.io, ghcr.io, quay.io)
|
||||
4. For dependencies (databases, redis, etc.), use official images (e.g., postgres:16, redis:7, etc.)
|
||||
5. Always specify image tags - avoid using 'latest' tag, use specific versions when possible
|
||||
6. Examples of correct image usage:
|
||||
- image: sendingtk/chatwoot:develop
|
||||
- image: postgres:16-alpine
|
||||
- image: redis:7-alpine
|
||||
- image: chatwoot/chatwoot:latest
|
||||
7. Examples of INCORRECT usage (DO NOT USE):
|
||||
- build: .
|
||||
- build: ./app
|
||||
- build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
Note: configFiles is optional - only include it if configuration files are absolutely required.
|
||||
|
||||
Volume Mounting and Configuration Rules:
|
||||
1. DO NOT create configuration files unless the service CANNOT work without them
|
||||
2. Most services can work with just environment variables - USE THEM FIRST
|
||||
3. Ask yourself: "Can this be configured with an environment variable instead?"
|
||||
4. If and ONLY IF a config file is absolutely required:
|
||||
- Keep it minimal with only critical settings
|
||||
- Use "../files/" prefix for all mounts
|
||||
- Format: "../files/folder:/container/path"
|
||||
5. DO NOT add configuration files for:
|
||||
- Default configurations that work out of the box
|
||||
- Settings that can be handled by environment variables
|
||||
- Proxy or routing configurations (these are handled elsewhere)
|
||||
Follow these rules:
|
||||
|
||||
Environment Variables Rules:
|
||||
1. For the envVariables array, provide ACTUAL example values, not placeholders
|
||||
2. Use realistic example values (e.g., "admin@example.com" for emails, "mypassword123" for passwords)
|
||||
3. DO NOT use \${VARIABLE_NAME-default} syntax in the envVariables values
|
||||
4. ONLY include environment variables that are actually used in the docker-compose
|
||||
5. Every environment variable referenced in the docker-compose MUST have a corresponding entry in envVariables
|
||||
6. Do not include environment variables for services that don't exist in the docker-compose
|
||||
|
||||
For each service that needs to be exposed to the internet:
|
||||
1. Define a domain configuration with:
|
||||
- host: the domain name for the service in format: {service-name}-{random-3-chars-hex}-${ip ? ip.replaceAll(".", "-") : ""}.traefik.me
|
||||
- port: the internal port the service runs on
|
||||
- serviceName: the name of the service in the docker-compose
|
||||
2. Make sure the service is properly configured to work with the specified port
|
||||
|
||||
User's original request: ${input}
|
||||
|
||||
Project details:
|
||||
${suggestion?.description}
|
||||
`,
|
||||
Docker Compose Rules:
|
||||
1. Use placeholder like \${VARIABLE_NAME-default} for generated variables in the docker-compose.yml
|
||||
2. Use complex values for passwords/secrets variables
|
||||
3. Don't set container_name field in services
|
||||
4. Don't set version field in the docker compose
|
||||
5. Don't set ports like 'ports: 3000:3000', use 'ports: "3000"' instead
|
||||
6. If a service depends on a database or other service, INCLUDE that service in the docker-compose
|
||||
7. Make sure all required services are defined in the docker-compose
|
||||
|
||||
Docker Image Rules (CRITICAL):
|
||||
1. ALWAYS use 'image:' field, NEVER use 'build:' field
|
||||
2. NEVER use 'build: .' or any build directive - we don't have local Dockerfiles
|
||||
3. Use images from Docker Hub or other public registries (e.g., docker.io, ghcr.io, quay.io)
|
||||
4. For dependencies (databases, redis, etc.), use official images (e.g., postgres:16, redis:7, etc.)
|
||||
5. Always specify image tags - avoid using 'latest' tag, use specific versions when possible
|
||||
6. Examples of correct image usage:
|
||||
- image: sendingtk/chatwoot:develop
|
||||
- image: postgres:16-alpine
|
||||
- image: redis:7-alpine
|
||||
- image: chatwoot/chatwoot:latest
|
||||
7. Examples of INCORRECT usage (DO NOT USE):
|
||||
- build: .
|
||||
- build: ./app
|
||||
- build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
|
||||
Volume Mounting and Configuration Rules:
|
||||
1. DO NOT create configuration files unless the service CANNOT work without them
|
||||
2. Most services can work with just environment variables - USE THEM FIRST
|
||||
3. Ask yourself: "Can this be configured with an environment variable instead?"
|
||||
4. If and ONLY IF a config file is absolutely required:
|
||||
- Keep it minimal with only critical settings
|
||||
- Use "../files/" prefix for all mounts
|
||||
- Format: "../files/folder:/container/path"
|
||||
5. DO NOT add configuration files for:
|
||||
- Default configurations that work out of the box
|
||||
- Settings that can be handled by environment variables
|
||||
- Proxy or routing configurations (these are handled elsewhere)
|
||||
|
||||
Environment Variables Rules:
|
||||
1. For the envVariables array, provide ACTUAL example values, not placeholders
|
||||
2. Use realistic example values (e.g., "admin@example.com" for emails, "mypassword123" for passwords)
|
||||
3. DO NOT use \${VARIABLE_NAME-default} syntax in the envVariables values
|
||||
4. ONLY include environment variables that are actually used in the docker-compose
|
||||
5. Every environment variable referenced in the docker-compose MUST have a corresponding entry in envVariables
|
||||
6. Do not include environment variables for services that don't exist in the docker-compose
|
||||
|
||||
For each service that needs to be exposed to the internet:
|
||||
1. Define a domain configuration with:
|
||||
- host: the domain name for the service in format: {service-name}-{random-3-chars-hex}-${ip ? ip.replaceAll(".", "-") : ""}.traefik.me
|
||||
- port: the internal port the service runs on
|
||||
- serviceName: the name of the service in the docker-compose
|
||||
2. Make sure the service is properly configured to work with the specified port
|
||||
|
||||
User's original request: ${input}
|
||||
|
||||
Project details:
|
||||
${suggestion?.description}
|
||||
`,
|
||||
});
|
||||
if (!!docker && !!docker.dockerCompose) {
|
||||
const docker = dockerResult.output as DockerOutput | undefined;
|
||||
if (docker?.dockerCompose) {
|
||||
result.push({
|
||||
...suggestion,
|
||||
...docker,
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"dokploy",
|
||||
"config",
|
||||
"dist",
|
||||
".next",
|
||||
"webpack.config.server.js",
|
||||
"migration.ts",
|
||||
"setup.ts"
|
||||
|
||||
7
packages/server/tsconfig.server.no-decl.json
Normal file
7
packages/server/tsconfig.server.no-decl.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.server.json",
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"declarationMap": false
|
||||
}
|
||||
}
|
||||
4517
pnpm-lock.yaml
generated
4517
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user