mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge pull request #829 from Dokploy/refactor/enhancement-languages
refactor: improve I18N
This commit is contained in:
commit
5f71a393be
@ -99,14 +99,14 @@ workflows:
|
|||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- canary
|
- canary
|
||||||
- fix/build-i18n
|
- refactor/enhancement-languages
|
||||||
- build-arm64:
|
- build-arm64:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- canary
|
- canary
|
||||||
- fix/build-i18n
|
- refactor/enhancement-languages
|
||||||
- combine-manifests:
|
- combine-manifests:
|
||||||
requires:
|
requires:
|
||||||
- build-amd64
|
- build-amd64
|
||||||
@ -116,4 +116,4 @@ workflows:
|
|||||||
only:
|
only:
|
||||||
- main
|
- main
|
||||||
- canary
|
- canary
|
||||||
- fix/build-i18n
|
- refactor/enhancement-languages
|
||||||
|
@ -35,7 +35,7 @@ RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var
|
|||||||
COPY --from=build /prod/dokploy/.next ./.next
|
COPY --from=build /prod/dokploy/.next ./.next
|
||||||
COPY --from=build /prod/dokploy/dist ./dist
|
COPY --from=build /prod/dokploy/dist ./dist
|
||||||
COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs
|
COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs
|
||||||
COPY --from=build /prod/dokploy/next-i18next.config.cjs ./next-i18next.config.cjs
|
# COPY --from=build /prod/dokploy/next-i18next.config.cjs ./next-i18next.config.cjs
|
||||||
COPY --from=build /prod/dokploy/public ./public
|
COPY --from=build /prod/dokploy/public ./public
|
||||||
COPY --from=build /prod/dokploy/package.json ./package.json
|
COPY --from=build /prod/dokploy/package.json ./package.json
|
||||||
COPY --from=build /prod/dokploy/drizzle ./drizzle
|
COPY --from=build /prod/dokploy/drizzle ./drizzle
|
||||||
|
@ -26,6 +26,7 @@ if (typeof window === "undefined") {
|
|||||||
|
|
||||||
const baseApp: ApplicationNested = {
|
const baseApp: ApplicationNested = {
|
||||||
applicationId: "",
|
applicationId: "",
|
||||||
|
herokuVersion: "",
|
||||||
applicationStatus: "done",
|
applicationStatus: "done",
|
||||||
appName: "",
|
appName: "",
|
||||||
autoDeploy: true,
|
autoDeploy: true,
|
||||||
|
@ -6,6 +6,7 @@ import { expect, test } from "vitest";
|
|||||||
|
|
||||||
const baseApp: ApplicationNested = {
|
const baseApp: ApplicationNested = {
|
||||||
applicationId: "",
|
applicationId: "",
|
||||||
|
herokuVersion: "",
|
||||||
applicationStatus: "done",
|
applicationStatus: "done",
|
||||||
appName: "",
|
appName: "",
|
||||||
autoDeploy: true,
|
autoDeploy: true,
|
||||||
|
@ -68,7 +68,7 @@ export const ComposeActions = ({ composeId }: Props) => {
|
|||||||
Open Terminal
|
Open Terminal
|
||||||
</Button>
|
</Button>
|
||||||
</DockerTerminalModal>
|
</DockerTerminalModal>
|
||||||
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
|
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
|
||||||
<span className="text-sm font-medium">Autodeploy</span>
|
<span className="text-sm font-medium">Autodeploy</span>
|
||||||
<Switch
|
<Switch
|
||||||
aria-label="Toggle italic"
|
aria-label="Toggle italic"
|
||||||
|
@ -1,94 +1,96 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
const Terminal = dynamic(
|
const Terminal = dynamic(
|
||||||
() => import("./docker-terminal").then((e) => e.DockerTerminal),
|
() => import("./docker-terminal").then((e) => e.DockerTerminal),
|
||||||
{
|
{
|
||||||
ssr: false,
|
ssr: false,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
containerId: string;
|
containerId: string;
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DockerTerminalModal = ({
|
export const DockerTerminalModal = ({
|
||||||
children,
|
children,
|
||||||
containerId,
|
containerId,
|
||||||
serverId,
|
serverId,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [mainDialogOpen, setMainDialogOpen] = useState(false);
|
const [mainDialogOpen, setMainDialogOpen] = useState(false);
|
||||||
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
|
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
|
||||||
|
|
||||||
const handleMainDialogOpenChange = (open: boolean) => {
|
const handleMainDialogOpenChange = (open: boolean) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setConfirmDialogOpen(true);
|
setConfirmDialogOpen(true);
|
||||||
} else {
|
} else {
|
||||||
setMainDialogOpen(true);
|
setMainDialogOpen(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
setConfirmDialogOpen(false);
|
setConfirmDialogOpen(false);
|
||||||
setMainDialogOpen(false);
|
setMainDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setConfirmDialogOpen(false);
|
setConfirmDialogOpen(false);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Dialog open={mainDialogOpen} onOpenChange={handleMainDialogOpenChange}>
|
<Dialog open={mainDialogOpen} onOpenChange={handleMainDialogOpenChange}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="w-full cursor-pointer space-x-3"
|
className="w-full cursor-pointer space-x-3"
|
||||||
onSelect={(e) => e.preventDefault()}
|
onSelect={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl">
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Docker Terminal</DialogTitle>
|
<DialogTitle>Docker Terminal</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Easy way to access to docker container
|
Easy way to access to docker container
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<Terminal
|
<Terminal
|
||||||
id="terminal"
|
id="terminal"
|
||||||
containerId={containerId}
|
containerId={containerId}
|
||||||
serverId={serverId || ""}
|
serverId={serverId || ""}
|
||||||
/>
|
/>
|
||||||
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
|
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Are you sure you want to close the terminal?</DialogTitle>
|
<DialogTitle>
|
||||||
<DialogDescription>
|
Are you sure you want to close the terminal?
|
||||||
By clicking the confirm button, the terminal will be closed.
|
</DialogTitle>
|
||||||
</DialogDescription>
|
<DialogDescription>
|
||||||
</DialogHeader>
|
By clicking the confirm button, the terminal will be closed.
|
||||||
<DialogFooter>
|
</DialogDescription>
|
||||||
<Button variant="outline" onClick={handleCancel}>
|
</DialogHeader>
|
||||||
Cancel
|
<DialogFooter>
|
||||||
</Button>
|
<Button variant="outline" onClick={handleCancel}>
|
||||||
<Button onClick={handleConfirm}>Confirm</Button>
|
Cancel
|
||||||
</DialogFooter>
|
</Button>
|
||||||
</DialogContent>
|
<Button onClick={handleConfirm}>Confirm</Button>
|
||||||
</Dialog>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { Languages } from "@/lib/languages";
|
||||||
import useLocale from "@/utils/hooks/use-locale";
|
import useLocale from "@/utils/hooks/use-locale";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
@ -37,25 +38,9 @@ const appearanceFormSchema = z.object({
|
|||||||
theme: z.enum(["light", "dark", "system"], {
|
theme: z.enum(["light", "dark", "system"], {
|
||||||
required_error: "Please select a theme.",
|
required_error: "Please select a theme.",
|
||||||
}),
|
}),
|
||||||
language: z.enum(
|
language: z.nativeEnum(Languages, {
|
||||||
[
|
required_error: "Please select a language.",
|
||||||
"en",
|
}),
|
||||||
"pl",
|
|
||||||
"ru",
|
|
||||||
"fr",
|
|
||||||
"de",
|
|
||||||
"tr",
|
|
||||||
"zh-Hant",
|
|
||||||
"kz",
|
|
||||||
"zh-Hans",
|
|
||||||
"fa",
|
|
||||||
"ko",
|
|
||||||
"pt-br",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
required_error: "Please select a language.",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
||||||
@ -63,7 +48,7 @@ type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
|||||||
// This can come from your database or API.
|
// This can come from your database or API.
|
||||||
const defaultValues: Partial<AppearanceFormValues> = {
|
const defaultValues: Partial<AppearanceFormValues> = {
|
||||||
theme: "system",
|
theme: "system",
|
||||||
language: "en",
|
language: Languages.English,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AppearanceForm() {
|
export function AppearanceForm() {
|
||||||
@ -188,25 +173,15 @@ export function AppearanceForm() {
|
|||||||
<SelectValue placeholder="No preset selected" />
|
<SelectValue placeholder="No preset selected" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{[
|
{Object.keys(Languages).map((preset) => {
|
||||||
{ label: "English", value: "en" },
|
const value =
|
||||||
{ label: "Polski", value: "pl" },
|
Languages[preset as keyof typeof Languages];
|
||||||
{ label: "Русский", value: "ru" },
|
return (
|
||||||
{ label: "Français", value: "fr" },
|
<SelectItem key={value} value={value}>
|
||||||
{ label: "Deutsch", value: "de" },
|
{preset}
|
||||||
{ label: "繁體中文", value: "zh-Hant" },
|
</SelectItem>
|
||||||
{ label: "简体中文", value: "zh-Hans" },
|
);
|
||||||
{ label: "Türkçe", value: "tr" },
|
})}
|
||||||
{ label: "Қазақ", value: "tr" },
|
|
||||||
{ label: "Kazakh", value: "kz" },
|
|
||||||
{ label: "Persian", value: "fa" },
|
|
||||||
{ label: "한국어", value: "ko" },
|
|
||||||
{ label: "Português", value: "pt-br" },
|
|
||||||
].map((preset) => (
|
|
||||||
<SelectItem key={preset.label} value={preset.value}>
|
|
||||||
{preset.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectGroup,
|
SelectGroup,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectLabel,
|
SelectLabel,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
@ -25,118 +25,118 @@ import type React from "react";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const Terminal = dynamic(
|
const Terminal = dynamic(
|
||||||
() =>
|
() =>
|
||||||
import("@/components/dashboard/docker/terminal/docker-terminal").then(
|
import("@/components/dashboard/docker/terminal/docker-terminal").then(
|
||||||
(e) => e.DockerTerminal
|
(e) => e.DockerTerminal,
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
ssr: false,
|
ssr: false,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
appName: string;
|
appName: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DockerTerminalModal = ({ children, appName, serverId }: Props) => {
|
export const DockerTerminalModal = ({ children, appName, serverId }: Props) => {
|
||||||
const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
|
const { data, isLoading } = api.docker.getContainersByAppNameMatch.useQuery(
|
||||||
{
|
{
|
||||||
appName,
|
appName,
|
||||||
serverId,
|
serverId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!appName,
|
enabled: !!appName,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const [containerId, setContainerId] = useState<string | undefined>();
|
const [containerId, setContainerId] = useState<string | undefined>();
|
||||||
const [mainDialogOpen, setMainDialogOpen] = useState(false);
|
const [mainDialogOpen, setMainDialogOpen] = useState(false);
|
||||||
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
|
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
|
||||||
|
|
||||||
const handleMainDialogOpenChange = (open: boolean) => {
|
const handleMainDialogOpenChange = (open: boolean) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setConfirmDialogOpen(true);
|
setConfirmDialogOpen(true);
|
||||||
} else {
|
} else {
|
||||||
setMainDialogOpen(true);
|
setMainDialogOpen(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
setConfirmDialogOpen(false);
|
setConfirmDialogOpen(false);
|
||||||
setMainDialogOpen(false);
|
setMainDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setConfirmDialogOpen(false);
|
setConfirmDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data && data?.length > 0) {
|
if (data && data?.length > 0) {
|
||||||
setContainerId(data[0]?.containerId);
|
setContainerId(data[0]?.containerId);
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={mainDialogOpen} onOpenChange={handleMainDialogOpenChange}>
|
<Dialog open={mainDialogOpen} onOpenChange={handleMainDialogOpenChange}>
|
||||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||||
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-7xl">
|
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-7xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Docker Terminal</DialogTitle>
|
<DialogTitle>Docker Terminal</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Easy way to access to docker container
|
Easy way to access to docker container
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Label>Select a container to view logs</Label>
|
<Label>Select a container to view logs</Label>
|
||||||
<Select onValueChange={setContainerId} value={containerId}>
|
<Select onValueChange={setContainerId} value={containerId}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
|
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground">
|
||||||
<span>Loading...</span>
|
<span>Loading...</span>
|
||||||
<Loader2 className="animate-spin size-4" />
|
<Loader2 className="animate-spin size-4" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<SelectValue placeholder="Select a container" />
|
<SelectValue placeholder="Select a container" />
|
||||||
)}
|
)}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{data?.map((container) => (
|
{data?.map((container) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={container.containerId}
|
key={container.containerId}
|
||||||
value={container.containerId}
|
value={container.containerId}
|
||||||
>
|
>
|
||||||
{container.name} ({container.containerId}) {container.state}
|
{container.name} ({container.containerId}) {container.state}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>Containers ({data?.length})</SelectLabel>
|
<SelectLabel>Containers ({data?.length})</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Terminal
|
<Terminal
|
||||||
serverId={serverId || ""}
|
serverId={serverId || ""}
|
||||||
id="terminal"
|
id="terminal"
|
||||||
containerId={containerId || "select-a-container"}
|
containerId={containerId || "select-a-container"}
|
||||||
/>
|
/>
|
||||||
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
|
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
Are you sure you want to close the terminal?
|
Are you sure you want to close the terminal?
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
By clicking the confirm button, the terminal will be closed.
|
By clicking the confirm button, the terminal will be closed.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={handleCancel}>
|
<Button variant="outline" onClick={handleCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleConfirm}>Confirm</Button>
|
<Button onClick={handleConfirm}>Confirm</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
16
apps/dokploy/lib/languages.ts
Normal file
16
apps/dokploy/lib/languages.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export enum Languages {
|
||||||
|
English = "en",
|
||||||
|
Polish = "pl",
|
||||||
|
Russian = "ru",
|
||||||
|
French = "fr",
|
||||||
|
German = "de",
|
||||||
|
ChineseTraditional = "zh-Hant",
|
||||||
|
ChineseSimplified = "zh-Hans",
|
||||||
|
Turkish = "tr",
|
||||||
|
Kazakh = "kz",
|
||||||
|
Persian = "fa",
|
||||||
|
Korean = "ko",
|
||||||
|
Portuguese = "pt-br",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Language = keyof typeof Languages;
|
@ -1,23 +1,23 @@
|
|||||||
/** @type {import('next-i18next').UserConfig} */
|
/** @type {import('next-i18next').UserConfig} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
fallbackLng: "en",
|
fallbackLng: "en",
|
||||||
keySeparator: false,
|
keySeparator: false,
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: "en",
|
defaultLocale: "en",
|
||||||
locales: [
|
locales: [
|
||||||
"en",
|
"en",
|
||||||
"pl",
|
"pl",
|
||||||
"ru",
|
"ru",
|
||||||
"fr",
|
"fr",
|
||||||
"de",
|
"de",
|
||||||
"tr",
|
"tr",
|
||||||
"kz",
|
"kz",
|
||||||
"zh-Hant",
|
"zh-Hant",
|
||||||
"zh-Hans",
|
"zh-Hans",
|
||||||
"fa",
|
"fa",
|
||||||
"ko",
|
"ko",
|
||||||
"pt-br",
|
"pt-br",
|
||||||
],
|
],
|
||||||
localeDetection: false,
|
localeDetection: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
|
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
|
import { Languages } from "@/lib/languages";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
import { appWithTranslation } from "next-i18next";
|
import { appWithTranslation } from "next-i18next";
|
||||||
@ -71,20 +72,7 @@ export default api.withTRPC(
|
|||||||
{
|
{
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: "en",
|
defaultLocale: "en",
|
||||||
locales: [
|
locales: Object.values(Languages),
|
||||||
"en",
|
|
||||||
"pl",
|
|
||||||
"ru",
|
|
||||||
"fr",
|
|
||||||
"de",
|
|
||||||
"tr",
|
|
||||||
"kz",
|
|
||||||
"zh-Hant",
|
|
||||||
"zh-Hans",
|
|
||||||
"fa",
|
|
||||||
"ko",
|
|
||||||
"pt-br",
|
|
||||||
],
|
|
||||||
localeDetection: false,
|
localeDetection: false,
|
||||||
},
|
},
|
||||||
fallbackLng: "en",
|
fallbackLng: "en",
|
||||||
|
@ -10,142 +10,141 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||||||
import { extractCommitMessage, extractHash } from "./[refreshToken]";
|
import { extractCommitMessage, extractHash } from "./[refreshToken]";
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse
|
res: NextApiResponse,
|
||||||
) {
|
) {
|
||||||
const signature = req.headers["x-hub-signature-256"];
|
const signature = req.headers["x-hub-signature-256"];
|
||||||
const githubBody = req.body;
|
const githubBody = req.body;
|
||||||
|
|
||||||
if (!githubBody?.installation?.id) {
|
if (!githubBody?.installation?.id) {
|
||||||
res.status(400).json({ message: "Github Installation not found" });
|
res.status(400).json({ message: "Github Installation not found" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const githubResult = await db.query.github.findFirst({
|
const githubResult = await db.query.github.findFirst({
|
||||||
where: eq(github.githubInstallationId, githubBody.installation.id),
|
where: eq(github.githubInstallationId, githubBody.installation.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!githubResult) {
|
if (!githubResult) {
|
||||||
res.status(400).json({ message: "Github Installation not found" });
|
res.status(400).json({ message: "Github Installation not found" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!githubResult.githubWebhookSecret) {
|
if (!githubResult.githubWebhookSecret) {
|
||||||
res.status(400).json({ message: "Github Webhook Secret not set" });
|
res.status(400).json({ message: "Github Webhook Secret not set" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const webhooks = new Webhooks({
|
const webhooks = new Webhooks({
|
||||||
secret: githubResult.githubWebhookSecret,
|
secret: githubResult.githubWebhookSecret,
|
||||||
});
|
});
|
||||||
|
|
||||||
const verified = await webhooks.verify(
|
const verified = await webhooks.verify(
|
||||||
JSON.stringify(githubBody),
|
JSON.stringify(githubBody),
|
||||||
signature as string
|
signature as string,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
res.status(401).json({ message: "Unauthorized" });
|
res.status(401).json({ message: "Unauthorized" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.headers["x-github-event"] === "ping") {
|
if (req.headers["x-github-event"] === "ping") {
|
||||||
res.status(200).json({ message: "Ping received, webhook is active" });
|
res.status(200).json({ message: "Ping received, webhook is active" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.headers["x-github-event"] !== "push") {
|
if (req.headers["x-github-event"] !== "push") {
|
||||||
res.status(400).json({ message: "We only accept push events" });
|
res.status(400).json({ message: "We only accept push events" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const branchName = githubBody?.ref?.replace("refs/heads/", "");
|
const branchName = githubBody?.ref?.replace("refs/heads/", "");
|
||||||
const repository = githubBody?.repository?.name;
|
const repository = githubBody?.repository?.name;
|
||||||
const deploymentTitle = extractCommitMessage(req.headers, req.body);
|
const deploymentTitle = extractCommitMessage(req.headers, req.body);
|
||||||
const deploymentHash = extractHash(req.headers, req.body);
|
const deploymentHash = extractHash(req.headers, req.body);
|
||||||
const owner = githubBody?.repository?.owner?.name;
|
const owner = githubBody?.repository?.owner?.name;
|
||||||
|
|
||||||
|
const apps = await db.query.applications.findMany({
|
||||||
|
where: and(
|
||||||
|
eq(applications.sourceType, "github"),
|
||||||
|
eq(applications.autoDeploy, true),
|
||||||
|
eq(applications.branch, branchName),
|
||||||
|
eq(applications.repository, repository),
|
||||||
|
eq(applications.owner, owner),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
const apps = await db.query.applications.findMany({
|
for (const app of apps) {
|
||||||
where: and(
|
const jobData: DeploymentJob = {
|
||||||
eq(applications.sourceType, "github"),
|
applicationId: app.applicationId as string,
|
||||||
eq(applications.autoDeploy, true),
|
titleLog: deploymentTitle,
|
||||||
eq(applications.branch, branchName),
|
descriptionLog: `Hash: ${deploymentHash}`,
|
||||||
eq(applications.repository, repository),
|
type: "deploy",
|
||||||
eq(applications.owner, owner)
|
applicationType: "application",
|
||||||
),
|
server: !!app.serverId,
|
||||||
});
|
};
|
||||||
|
|
||||||
for (const app of apps) {
|
if (IS_CLOUD && app.serverId) {
|
||||||
const jobData: DeploymentJob = {
|
jobData.serverId = app.serverId;
|
||||||
applicationId: app.applicationId as string,
|
await deploy(jobData);
|
||||||
titleLog: deploymentTitle,
|
return true;
|
||||||
descriptionLog: `Hash: ${deploymentHash}`,
|
}
|
||||||
type: "deploy",
|
await myQueue.add(
|
||||||
applicationType: "application",
|
"deployments",
|
||||||
server: !!app.serverId,
|
{ ...jobData },
|
||||||
};
|
{
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (IS_CLOUD && app.serverId) {
|
const composeApps = await db.query.compose.findMany({
|
||||||
jobData.serverId = app.serverId;
|
where: and(
|
||||||
await deploy(jobData);
|
eq(compose.sourceType, "github"),
|
||||||
return true;
|
eq(compose.autoDeploy, true),
|
||||||
}
|
eq(compose.branch, branchName),
|
||||||
await myQueue.add(
|
eq(compose.repository, repository),
|
||||||
"deployments",
|
eq(compose.owner, owner),
|
||||||
{ ...jobData },
|
),
|
||||||
{
|
});
|
||||||
removeOnComplete: true,
|
|
||||||
removeOnFail: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const composeApps = await db.query.compose.findMany({
|
for (const composeApp of composeApps) {
|
||||||
where: and(
|
const jobData: DeploymentJob = {
|
||||||
eq(compose.sourceType, "github"),
|
composeId: composeApp.composeId as string,
|
||||||
eq(compose.autoDeploy, true),
|
titleLog: deploymentTitle,
|
||||||
eq(compose.branch, branchName),
|
type: "deploy",
|
||||||
eq(compose.repository, repository),
|
applicationType: "compose",
|
||||||
eq(compose.owner, owner)
|
descriptionLog: `Hash: ${deploymentHash}`,
|
||||||
),
|
server: !!composeApp.serverId,
|
||||||
});
|
};
|
||||||
|
|
||||||
for (const composeApp of composeApps) {
|
if (IS_CLOUD && composeApp.serverId) {
|
||||||
const jobData: DeploymentJob = {
|
jobData.serverId = composeApp.serverId;
|
||||||
composeId: composeApp.composeId as string,
|
await deploy(jobData);
|
||||||
titleLog: deploymentTitle,
|
return true;
|
||||||
type: "deploy",
|
}
|
||||||
applicationType: "compose",
|
|
||||||
descriptionLog: `Hash: ${deploymentHash}`,
|
|
||||||
server: !!composeApp.serverId,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (IS_CLOUD && composeApp.serverId) {
|
await myQueue.add(
|
||||||
jobData.serverId = composeApp.serverId;
|
"deployments",
|
||||||
await deploy(jobData);
|
{ ...jobData },
|
||||||
return true;
|
{
|
||||||
}
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await myQueue.add(
|
const totalApps = apps.length + composeApps.length;
|
||||||
"deployments",
|
const emptyApps = totalApps === 0;
|
||||||
{ ...jobData },
|
|
||||||
{
|
|
||||||
removeOnComplete: true,
|
|
||||||
removeOnFail: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalApps = apps.length + composeApps.length;
|
if (emptyApps) {
|
||||||
const emptyApps = totalApps === 0;
|
res.status(200).json({ message: "No apps to deploy" });
|
||||||
|
return;
|
||||||
if (emptyApps) {
|
}
|
||||||
res.status(200).json({ message: "No apps to deploy" });
|
res.status(200).json({ message: `Deployed ${totalApps} apps` });
|
||||||
return;
|
} catch (error) {
|
||||||
}
|
res.status(400).json({ message: "Error To Deploy Application", error });
|
||||||
res.status(200).json({ message: `Deployed ${totalApps} apps` });
|
}
|
||||||
} catch (error) {
|
|
||||||
res.status(400).json({ message: "Error To Deploy Application", error });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,44 +1,44 @@
|
|||||||
{
|
{
|
||||||
"settings.common.save": "저장",
|
"settings.common.save": "저장",
|
||||||
"settings.server.domain.title": "서버 도메인",
|
"settings.server.domain.title": "서버 도메인",
|
||||||
"settings.server.domain.description": "서버 애플리케이션에 도메인을 추가합니다.",
|
"settings.server.domain.description": "서버 애플리케이션에 도메인을 추가합니다.",
|
||||||
"settings.server.domain.form.domain": "도메인",
|
"settings.server.domain.form.domain": "도메인",
|
||||||
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt 이메일",
|
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt 이메일",
|
||||||
"settings.server.domain.form.certificate.label": "인증서",
|
"settings.server.domain.form.certificate.label": "인증서",
|
||||||
"settings.server.domain.form.certificate.placeholder": "인증서 선택",
|
"settings.server.domain.form.certificate.placeholder": "인증서 선택",
|
||||||
"settings.server.domain.form.certificateOptions.none": "없음",
|
"settings.server.domain.form.certificateOptions.none": "없음",
|
||||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (기본)",
|
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (기본)",
|
||||||
|
|
||||||
"settings.server.webServer.title": "웹 서버",
|
"settings.server.webServer.title": "웹 서버",
|
||||||
"settings.server.webServer.description": "웹 서버를 재시작하거나 정리합니다.",
|
"settings.server.webServer.description": "웹 서버를 재시작하거나 정리합니다.",
|
||||||
"settings.server.webServer.actions": "작업",
|
"settings.server.webServer.actions": "작업",
|
||||||
"settings.server.webServer.reload": "재시작",
|
"settings.server.webServer.reload": "재시작",
|
||||||
"settings.server.webServer.watchLogs": "로그 보기",
|
"settings.server.webServer.watchLogs": "로그 보기",
|
||||||
"settings.server.webServer.updateServerIp": "서버 IP 갱신",
|
"settings.server.webServer.updateServerIp": "서버 IP 갱신",
|
||||||
"settings.server.webServer.server.label": "서버",
|
"settings.server.webServer.server.label": "서버",
|
||||||
"settings.server.webServer.traefik.label": "Traefik",
|
"settings.server.webServer.traefik.label": "Traefik",
|
||||||
"settings.server.webServer.traefik.modifyEnv": "환경 변수 수정",
|
"settings.server.webServer.traefik.modifyEnv": "환경 변수 수정",
|
||||||
"settings.server.webServer.storage.label": "저장 공간",
|
"settings.server.webServer.storage.label": "저장 공간",
|
||||||
"settings.server.webServer.storage.cleanUnusedImages": "사용하지 않는 이미지 정리",
|
"settings.server.webServer.storage.cleanUnusedImages": "사용하지 않는 이미지 정리",
|
||||||
"settings.server.webServer.storage.cleanUnusedVolumes": "사용하지 않는 볼륨 정리",
|
"settings.server.webServer.storage.cleanUnusedVolumes": "사용하지 않는 볼륨 정리",
|
||||||
"settings.server.webServer.storage.cleanStoppedContainers": "정지된 컨테이너 정리",
|
"settings.server.webServer.storage.cleanStoppedContainers": "정지된 컨테이너 정리",
|
||||||
"settings.server.webServer.storage.cleanDockerBuilder": "도커 빌더 & 시스템 정리",
|
"settings.server.webServer.storage.cleanDockerBuilder": "도커 빌더 & 시스템 정리",
|
||||||
"settings.server.webServer.storage.cleanMonitoring": "모니터링 데이터 정리",
|
"settings.server.webServer.storage.cleanMonitoring": "모니터링 데이터 정리",
|
||||||
"settings.server.webServer.storage.cleanAll": "전체 정리",
|
"settings.server.webServer.storage.cleanAll": "전체 정리",
|
||||||
|
|
||||||
"settings.profile.title": "계정",
|
"settings.profile.title": "계정",
|
||||||
"settings.profile.description": "여기에서 프로필 세부 정보를 변경하세요.",
|
"settings.profile.description": "여기에서 프로필 세부 정보를 변경하세요.",
|
||||||
"settings.profile.email": "이메일",
|
"settings.profile.email": "이메일",
|
||||||
"settings.profile.password": "비밀번호",
|
"settings.profile.password": "비밀번호",
|
||||||
"settings.profile.avatar": "아바타",
|
"settings.profile.avatar": "아바타",
|
||||||
|
|
||||||
"settings.appearance.title": "외관",
|
"settings.appearance.title": "외관",
|
||||||
"settings.appearance.description": "대시보드의 테마를 사용자 설정합니다.",
|
"settings.appearance.description": "대시보드의 테마를 사용자 설정합니다.",
|
||||||
"settings.appearance.theme": "테마",
|
"settings.appearance.theme": "테마",
|
||||||
"settings.appearance.themeDescription": "대시보드 테마 선택",
|
"settings.appearance.themeDescription": "대시보드 테마 선택",
|
||||||
"settings.appearance.themes.light": "라이트",
|
"settings.appearance.themes.light": "라이트",
|
||||||
"settings.appearance.themes.dark": "다크",
|
"settings.appearance.themes.dark": "다크",
|
||||||
"settings.appearance.themes.system": "시스템",
|
"settings.appearance.themes.system": "시스템",
|
||||||
"settings.appearance.language": "언어",
|
"settings.appearance.language": "언어",
|
||||||
"settings.appearance.languageDescription": "대시보드에서 사용할 언어 선택"
|
"settings.appearance.languageDescription": "대시보드에서 사용할 언어 선택"
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
{}
|
{}
|
||||||
|
@ -1,41 +1,41 @@
|
|||||||
{
|
{
|
||||||
"settings.common.save": "Сақтау",
|
"settings.common.save": "Сақтау",
|
||||||
"settings.server.domain.title": "Сервер домені",
|
"settings.server.domain.title": "Сервер домені",
|
||||||
"settings.server.domain.description": "Dokploy сервер қолданбасына домен енгізіңіз.",
|
"settings.server.domain.description": "Dokploy сервер қолданбасына домен енгізіңіз.",
|
||||||
"settings.server.domain.form.domain": "Домен",
|
"settings.server.domain.form.domain": "Домен",
|
||||||
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt Эл. поштасы",
|
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt Эл. поштасы",
|
||||||
"settings.server.domain.form.certificate.label": "Сертификат",
|
"settings.server.domain.form.certificate.label": "Сертификат",
|
||||||
"settings.server.domain.form.certificate.placeholder": "Сертификатты таңдаңыз",
|
"settings.server.domain.form.certificate.placeholder": "Сертификатты таңдаңыз",
|
||||||
"settings.server.domain.form.certificateOptions.none": "Жоқ",
|
"settings.server.domain.form.certificateOptions.none": "Жоқ",
|
||||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Стандартты)",
|
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Стандартты)",
|
||||||
"settings.server.webServer.title": "Веб-Сервер",
|
"settings.server.webServer.title": "Веб-Сервер",
|
||||||
"settings.server.webServer.description": "Веб-серверді қайта жүктеу немесе тазалау.",
|
"settings.server.webServer.description": "Веб-серверді қайта жүктеу немесе тазалау.",
|
||||||
"settings.server.webServer.actions": "Әрекеттер",
|
"settings.server.webServer.actions": "Әрекеттер",
|
||||||
"settings.server.webServer.reload": "Қайта жүктеу",
|
"settings.server.webServer.reload": "Қайта жүктеу",
|
||||||
"settings.server.webServer.watchLogs": "Журналдарды қарау",
|
"settings.server.webServer.watchLogs": "Журналдарды қарау",
|
||||||
"settings.server.webServer.updateServerIp": "Сервердің IP жаңарту",
|
"settings.server.webServer.updateServerIp": "Сервердің IP жаңарту",
|
||||||
"settings.server.webServer.server.label": "Сервер",
|
"settings.server.webServer.server.label": "Сервер",
|
||||||
"settings.server.webServer.traefik.label": "Traefik",
|
"settings.server.webServer.traefik.label": "Traefik",
|
||||||
"settings.server.webServer.traefik.modifyEnv": "Env Өзгерту",
|
"settings.server.webServer.traefik.modifyEnv": "Env Өзгерту",
|
||||||
"settings.server.webServer.storage.label": "Диск кеңістігі",
|
"settings.server.webServer.storage.label": "Диск кеңістігі",
|
||||||
"settings.server.webServer.storage.cleanUnusedImages": "Пайдаланылмаған образды тазалау",
|
"settings.server.webServer.storage.cleanUnusedImages": "Пайдаланылмаған образды тазалау",
|
||||||
"settings.server.webServer.storage.cleanUnusedVolumes": "Пайдаланылмаған томды тазалау",
|
"settings.server.webServer.storage.cleanUnusedVolumes": "Пайдаланылмаған томды тазалау",
|
||||||
"settings.server.webServer.storage.cleanStoppedContainers": "Тоқтатылған контейнерлерді тазалау",
|
"settings.server.webServer.storage.cleanStoppedContainers": "Тоқтатылған контейнерлерді тазалау",
|
||||||
"settings.server.webServer.storage.cleanDockerBuilder": "Docker Builder & Системаны тазалау",
|
"settings.server.webServer.storage.cleanDockerBuilder": "Docker Builder & Системаны тазалау",
|
||||||
"settings.server.webServer.storage.cleanMonitoring": "Мониторингті тазалау",
|
"settings.server.webServer.storage.cleanMonitoring": "Мониторингті тазалау",
|
||||||
"settings.server.webServer.storage.cleanAll": "Барлығын тазалау",
|
"settings.server.webServer.storage.cleanAll": "Барлығын тазалау",
|
||||||
"settings.profile.title": "Аккаунт",
|
"settings.profile.title": "Аккаунт",
|
||||||
"settings.profile.description": "Профиль мәліметтерін осы жерден өзгертіңіз.",
|
"settings.profile.description": "Профиль мәліметтерін осы жерден өзгертіңіз.",
|
||||||
"settings.profile.email": "Эл. пошта",
|
"settings.profile.email": "Эл. пошта",
|
||||||
"settings.profile.password": "Құпия сөз",
|
"settings.profile.password": "Құпия сөз",
|
||||||
"settings.profile.avatar": "Аватар",
|
"settings.profile.avatar": "Аватар",
|
||||||
"settings.appearance.title": "Сыртқы түрі",
|
"settings.appearance.title": "Сыртқы түрі",
|
||||||
"settings.appearance.description": "Dokploy сыртқы келбетін өзгерту.",
|
"settings.appearance.description": "Dokploy сыртқы келбетін өзгерту.",
|
||||||
"settings.appearance.theme": "Келбеті",
|
"settings.appearance.theme": "Келбеті",
|
||||||
"settings.appearance.themeDescription": "Жүйе тақтасының келбетің таңдаңыз",
|
"settings.appearance.themeDescription": "Жүйе тақтасының келбетің таңдаңыз",
|
||||||
"settings.appearance.themes.light": "Жарық",
|
"settings.appearance.themes.light": "Жарық",
|
||||||
"settings.appearance.themes.dark": "Қараңғы",
|
"settings.appearance.themes.dark": "Қараңғы",
|
||||||
"settings.appearance.themes.system": "Жүйелік",
|
"settings.appearance.themes.system": "Жүйелік",
|
||||||
"settings.appearance.language": "Тіл",
|
"settings.appearance.language": "Тіл",
|
||||||
"settings.appearance.languageDescription": "Жүйе тақтасының тілің таңдаңыз"
|
"settings.appearance.languageDescription": "Жүйе тақтасының тілің таңдаңыз"
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@
|
|||||||
"settings.profile.email": "Email",
|
"settings.profile.email": "Email",
|
||||||
"settings.profile.password": "Senha",
|
"settings.profile.password": "Senha",
|
||||||
"settings.profile.avatar": "Avatar",
|
"settings.profile.avatar": "Avatar",
|
||||||
|
|
||||||
"settings.appearance.title": "Aparência",
|
"settings.appearance.title": "Aparencia",
|
||||||
"settings.appearance.description": "Personalize o tema do seu dashboard.",
|
"settings.appearance.description": "Personalize o tema do seu dashboard.",
|
||||||
"settings.appearance.theme": "Tema",
|
"settings.appearance.theme": "Tema",
|
||||||
"settings.appearance.themeDescription": "Selecione um tema para o dashboard",
|
"settings.appearance.themeDescription": "Selecione um tema para o dashboard",
|
||||||
|
@ -190,7 +190,7 @@ export const notificationRouter = createTRPCRouter({
|
|||||||
await sendDiscordNotification(input, {
|
await sendDiscordNotification(input, {
|
||||||
title: "> `🤚` - Test Notification",
|
title: "> `🤚` - Test Notification",
|
||||||
description: "> Hi, From Dokploy 👋",
|
description: "> Hi, From Dokploy 👋",
|
||||||
color: 0xf3f7f4
|
color: 0xf3f7f4,
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,93 +1,93 @@
|
|||||||
import { Secrets } from "@/components/ui/secrets";
|
import { Secrets } from "@/components/ui/secrets";
|
||||||
import {
|
import {
|
||||||
type DomainSchema,
|
type DomainSchema,
|
||||||
type Schema,
|
type Schema,
|
||||||
type Template,
|
type Template,
|
||||||
generateBase64,
|
generateBase64,
|
||||||
generateRandomDomain,
|
generateRandomDomain,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
export function generate(schema: Schema): Template {
|
export function generate(schema: Schema): Template {
|
||||||
const triggerDomain = generateRandomDomain(schema);
|
const triggerDomain = generateRandomDomain(schema);
|
||||||
|
|
||||||
const magicLinkSecret = generateBase64(16);
|
const magicLinkSecret = generateBase64(16);
|
||||||
const sessionSecret = generateBase64(16);
|
const sessionSecret = generateBase64(16);
|
||||||
const encryptionKey = generateBase64(32);
|
const encryptionKey = generateBase64(32);
|
||||||
const providerSecret = generateBase64(32);
|
const providerSecret = generateBase64(32);
|
||||||
const coordinatorSecret = generateBase64(32);
|
const coordinatorSecret = generateBase64(32);
|
||||||
|
|
||||||
const dbPassword = generateBase64(24);
|
const dbPassword = generateBase64(24);
|
||||||
const dbUser = "triggeruser";
|
const dbUser = "triggeruser";
|
||||||
const dbName = "triggerdb";
|
const dbName = "triggerdb";
|
||||||
|
|
||||||
const domains: DomainSchema[] = [
|
const domains: DomainSchema[] = [
|
||||||
{
|
{
|
||||||
host: triggerDomain,
|
host: triggerDomain,
|
||||||
port: 3000,
|
port: 3000,
|
||||||
serviceName: "webapp",
|
serviceName: "webapp",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const envs = [
|
const envs = [
|
||||||
`NODE_ENV=production`,
|
"NODE_ENV=production",
|
||||||
`RUNTIME_PLATFORM=docker-compose`,
|
"RUNTIME_PLATFORM=docker-compose",
|
||||||
`V3_ENABLED=true`,
|
"V3_ENABLED=true",
|
||||||
|
|
||||||
`# Domain configuration`,
|
"# Domain configuration",
|
||||||
`TRIGGER_DOMAIN=${triggerDomain}`,
|
`TRIGGER_DOMAIN=${triggerDomain}`,
|
||||||
`TRIGGER_PROTOCOL=http`,
|
"TRIGGER_PROTOCOL=http",
|
||||||
|
|
||||||
`# Database configuration with secure credentials`,
|
"# Database configuration with secure credentials",
|
||||||
`POSTGRES_USER=${dbUser}`,
|
`POSTGRES_USER=${dbUser}`,
|
||||||
`POSTGRES_PASSWORD=${dbPassword}`,
|
`POSTGRES_PASSWORD=${dbPassword}`,
|
||||||
`POSTGRES_DB=${dbName}`,
|
`POSTGRES_DB=${dbName}`,
|
||||||
`DATABASE_URL=postgresql://${dbUser}:${dbPassword}@postgres:5432/${dbName}`,
|
`DATABASE_URL=postgresql://${dbUser}:${dbPassword}@postgres:5432/${dbName}`,
|
||||||
|
|
||||||
`# Secrets`,
|
"# Secrets",
|
||||||
`MAGIC_LINK_SECRET=${magicLinkSecret}`,
|
`MAGIC_LINK_SECRET=${magicLinkSecret}`,
|
||||||
`SESSION_SECRET=${sessionSecret}`,
|
`SESSION_SECRET=${sessionSecret}`,
|
||||||
`ENCRYPTION_KEY=${encryptionKey}`,
|
`ENCRYPTION_KEY=${encryptionKey}`,
|
||||||
`PROVIDER_SECRET=${providerSecret}`,
|
`PROVIDER_SECRET=${providerSecret}`,
|
||||||
`COORDINATOR_SECRET=${coordinatorSecret}`,
|
`COORDINATOR_SECRET=${coordinatorSecret}`,
|
||||||
|
|
||||||
`# TRIGGER_TELEMETRY_DISABLED=1`,
|
"# TRIGGER_TELEMETRY_DISABLED=1",
|
||||||
`INTERNAL_OTEL_TRACE_DISABLED=1`,
|
"INTERNAL_OTEL_TRACE_DISABLED=1",
|
||||||
`INTERNAL_OTEL_TRACE_LOGGING_ENABLED=0`,
|
"INTERNAL_OTEL_TRACE_LOGGING_ENABLED=0",
|
||||||
|
|
||||||
`DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT=300`,
|
"DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT=300",
|
||||||
`DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT=100`,
|
"DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT=100",
|
||||||
|
|
||||||
`DIRECT_URL=\${DATABASE_URL}`,
|
"DIRECT_URL=${DATABASE_URL}",
|
||||||
`REDIS_HOST=redis`,
|
"REDIS_HOST=redis",
|
||||||
`REDIS_PORT=6379`,
|
"REDIS_PORT=6379",
|
||||||
`REDIS_TLS_DISABLED=true`,
|
"REDIS_TLS_DISABLED=true",
|
||||||
|
|
||||||
`# If this is set, emails that are not specified won't be able to log in`,
|
"# If this is set, emails that are not specified won't be able to log in",
|
||||||
`# WHITELISTED_EMAILS="authorized@yahoo.com|authorized@gmail.com"`,
|
'# WHITELISTED_EMAILS="authorized@yahoo.com|authorized@gmail.com"',
|
||||||
`# Accounts with these emails will become admins when signing up and get access to the admin panel`,
|
"# Accounts with these emails will become admins when signing up and get access to the admin panel",
|
||||||
`# ADMIN_EMAILS="admin@example.com|another-admin@example.com"`,
|
'# ADMIN_EMAILS="admin@example.com|another-admin@example.com"',
|
||||||
|
|
||||||
`# If this is set, your users will be able to log in via GitHub`,
|
"# If this is set, your users will be able to log in via GitHub",
|
||||||
`# AUTH_GITHUB_CLIENT_ID=`,
|
"# AUTH_GITHUB_CLIENT_ID=",
|
||||||
`# AUTH_GITHUB_CLIENT_SECRET=`,
|
"# AUTH_GITHUB_CLIENT_SECRET=",
|
||||||
|
|
||||||
`# E-mail settings`,
|
"# E-mail settings",
|
||||||
`# Ensure the FROM_EMAIL matches what you setup with Resend.com`,
|
"# Ensure the FROM_EMAIL matches what you setup with Resend.com",
|
||||||
`# If these are not set, emails will be printed to the console`,
|
"# If these are not set, emails will be printed to the console",
|
||||||
`# FROM_EMAIL=`,
|
"# FROM_EMAIL=",
|
||||||
`# REPLY_TO_EMAIL=`,
|
"# REPLY_TO_EMAIL=",
|
||||||
`# RESEND_API_KEY=`,
|
"# RESEND_API_KEY=",
|
||||||
|
|
||||||
`# Worker settings`,
|
"# Worker settings",
|
||||||
`HTTP_SERVER_PORT=9020`,
|
"HTTP_SERVER_PORT=9020",
|
||||||
`COORDINATOR_HOST=127.0.0.1`,
|
"COORDINATOR_HOST=127.0.0.1",
|
||||||
`COORDINATOR_PORT=\${HTTP_SERVER_PORT}`,
|
"COORDINATOR_PORT=${HTTP_SERVER_PORT}",
|
||||||
`# REGISTRY_HOST=\${DEPLOY_REGISTRY_HOST}`,
|
"# REGISTRY_HOST=${DEPLOY_REGISTRY_HOST}",
|
||||||
`# REGISTRY_NAMESPACE=\${DEPLOY_REGISTRY_NAMESPACE}`,
|
"# REGISTRY_NAMESPACE=${DEPLOY_REGISTRY_NAMESPACE}",
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
envs,
|
envs,
|
||||||
domains,
|
domains,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,10 @@
|
|||||||
|
import type { Languages } from "@/lib/languages";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
const SUPPORTED_LOCALES = [
|
|
||||||
"en",
|
|
||||||
"pl",
|
|
||||||
"ru",
|
|
||||||
"fr",
|
|
||||||
"de",
|
|
||||||
"tr",
|
|
||||||
"kz",
|
|
||||||
"zh-Hant",
|
|
||||||
"zh-Hans",
|
|
||||||
"fa",
|
|
||||||
"ko",
|
|
||||||
"pt-br",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
type Locale = (typeof SUPPORTED_LOCALES)[number];
|
|
||||||
|
|
||||||
export default function useLocale() {
|
export default function useLocale() {
|
||||||
const currentLocale = (Cookies.get("DOKPLOY_LOCALE") ?? "en") as Locale;
|
const currentLocale = (Cookies.get("DOKPLOY_LOCALE") ?? "en") as Languages;
|
||||||
|
|
||||||
const setLocale = (locale: Locale) => {
|
const setLocale = (locale: Languages) => {
|
||||||
Cookies.set("DOKPLOY_LOCALE", locale, { expires: 365 });
|
Cookies.set("DOKPLOY_LOCALE", locale, { expires: 365 });
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
@ -5,11 +5,19 @@ export function getLocale(cookies: NextApiRequestCookies) {
|
|||||||
return locale;
|
return locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
// libs/i18n.js
|
import { Languages } from "@/lib/languages";
|
||||||
import { serverSideTranslations as originalServerSideTranslations } from "next-i18next/serverSideTranslations";
|
import { serverSideTranslations as originalServerSideTranslations } from "next-i18next/serverSideTranslations";
|
||||||
import nextI18NextConfig from "../next-i18next.config.cjs";
|
|
||||||
|
|
||||||
export const serverSideTranslations = (
|
export const serverSideTranslations = (
|
||||||
locale: string,
|
locale: string,
|
||||||
namespaces = ["common"],
|
namespaces = ["common"],
|
||||||
) => originalServerSideTranslations(locale, namespaces, nextI18NextConfig);
|
) =>
|
||||||
|
originalServerSideTranslations(locale, namespaces, {
|
||||||
|
fallbackLng: "en",
|
||||||
|
keySeparator: false,
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: "en",
|
||||||
|
locales: Object.values(Languages),
|
||||||
|
localeDetection: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -95,7 +95,9 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "`❓`・Type",
|
name: "`❓`・Type",
|
||||||
value: type.replace("error", "Failed").replace("success", "Successful"),
|
value: type
|
||||||
|
.replace("error", "Failed")
|
||||||
|
.replace("success", "Successful"),
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
...(type === "error" && errorMessage
|
...(type === "error" && errorMessage
|
||||||
|
@ -48,7 +48,6 @@ export const sendDockerCleanupNotifications = async (
|
|||||||
title: "> `✅` - Docker Cleanup",
|
title: "> `✅` - Docker Cleanup",
|
||||||
color: 0x57f287,
|
color: 0x57f287,
|
||||||
fields: [
|
fields: [
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "`📅`・Date",
|
name: "`📅`・Date",
|
||||||
value: date.toLocaleDateString(),
|
value: date.toLocaleDateString(),
|
||||||
|
Loading…
Reference in New Issue
Block a user