mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge pull request #714 from kdurek/feat/server-ip
feat: add update server ip
This commit is contained in:
commit
173110a415
@ -37,7 +37,7 @@ const appearanceFormSchema = z.object({
|
||||
theme: z.enum(["light", "dark", "system"], {
|
||||
required_error: "Please select a theme.",
|
||||
}),
|
||||
language: z.enum(["en", "zh-Hans"], {
|
||||
language: z.enum(["en", "pl", "zh-Hans"], {
|
||||
required_error: "Please select a language.",
|
||||
}),
|
||||
});
|
||||
@ -174,6 +174,7 @@ export function AppearanceForm() {
|
||||
<SelectContent>
|
||||
{[
|
||||
{ label: "English", value: "en" },
|
||||
{ label: "Polski", value: "pl" },
|
||||
{ label: "简体中文", value: "zh-Hans" },
|
||||
].map((preset) => (
|
||||
<SelectItem key={preset.label} value={preset.value}>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import React from "react";
|
||||
|
||||
import { UpdateServerIp } from "@/components/dashboard/settings/web-server/update-server-ip";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@ -44,13 +45,27 @@ export const ShowDokployActions = () => {
|
||||
toast.success("Server Reloaded");
|
||||
});
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span>{t("settings.server.webServer.reload")}</span>
|
||||
</DropdownMenuItem>
|
||||
<ShowModalLogs appName="dokploy">
|
||||
<span>{t("settings.server.webServer.watchLogs")}</span>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
{t("settings.server.webServer.watchLogs")}
|
||||
</DropdownMenuItem>
|
||||
</ShowModalLogs>
|
||||
<GPUSupportModal />
|
||||
<UpdateServerIp>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
{t("settings.server.webServer.updateServerIp")}
|
||||
</DropdownMenuItem>
|
||||
</UpdateServerIp>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { CardDescription, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@ -21,13 +20,13 @@ export const ShowServerActions = ({ serverId }: Props) => {
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer "
|
||||
className="w-full cursor-pointer"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
View Actions
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl overflow-y-auto max-h-screen ">
|
||||
<DialogContent className="sm:max-w-xl overflow-y-auto max-h-screen">
|
||||
<div className="flex flex-col gap-1">
|
||||
<DialogTitle className="text-xl">Web server settings</DialogTitle>
|
||||
<DialogDescription>Reload or clean the web server.</DialogDescription>
|
||||
|
@ -74,16 +74,22 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
|
||||
toast.error("Error to reload the traefik");
|
||||
});
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span>{t("settings.server.webServer.reload")}</span>
|
||||
</DropdownMenuItem>
|
||||
<ShowModalLogs appName="dokploy-traefik" serverId={serverId}>
|
||||
<span>{t("settings.server.webServer.watchLogs")}</span>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{t("settings.server.webServer.watchLogs")}
|
||||
</DropdownMenuItem>
|
||||
</ShowModalLogs>
|
||||
<EditTraefikEnv serverId={serverId}>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
className="w-full cursor-pointer space-x-3"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span>{t("settings.server.webServer.traefik.modifyEnv")}</span>
|
||||
</DropdownMenuItem>
|
||||
|
@ -58,14 +58,7 @@ export const ShowModalLogs = ({ appName, children, serverId }: Props) => {
|
||||
}, [data]);
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer space-x-3"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-7xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>View Logs</DialogTitle>
|
||||
|
@ -0,0 +1,161 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const schema = z.object({
|
||||
serverIp: z.string(),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof schema>;
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
serverId?: string;
|
||||
}
|
||||
|
||||
export const UpdateServerIp = ({ children, serverId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { data } = api.admin.one.useQuery();
|
||||
const { data: ip } = api.server.publicIp.useQuery();
|
||||
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
api.admin.update.useMutation();
|
||||
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
serverIp: data?.serverIp || "",
|
||||
},
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
serverIp: data.serverIp || "",
|
||||
});
|
||||
}
|
||||
}, [form, form.reset, data]);
|
||||
|
||||
const utils = api.useUtils();
|
||||
|
||||
const setCurrentIp = () => {
|
||||
if (!ip) return;
|
||||
form.setValue("serverIp", ip);
|
||||
};
|
||||
|
||||
const onSubmit = async (data: Schema) => {
|
||||
await mutateAsync({
|
||||
serverIp: data.serverIp,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Server IP Updated");
|
||||
await utils.admin.one.invalidate();
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update the IP of the server");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Update Server IP</DialogTitle>
|
||||
<DialogDescription>Update the IP of the server</DialogDescription>
|
||||
</DialogHeader>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-update-server-ip"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="serverIp"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Server IP</FormLabel>
|
||||
<FormControl className="flex gap-2">
|
||||
<div>
|
||||
<Input {...field} />
|
||||
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
onClick={setCurrentIp}
|
||||
>
|
||||
<RefreshCw className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
sideOffset={5}
|
||||
className="max-w-[11rem]"
|
||||
>
|
||||
<p>Set current public IP</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</FormControl>
|
||||
<pre>
|
||||
<FormMessage />
|
||||
</pre>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading}
|
||||
form="hook-form-update-server-ip"
|
||||
type="submit"
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
@ -87,7 +87,7 @@ export const UpdateServer = () => {
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Check updates
|
||||
Check Updates
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
module.exports = {
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: ["en", "zh-Hans"],
|
||||
locales: ["en", "pl", "zh-Hans"],
|
||||
localeDetection: false,
|
||||
},
|
||||
fallbackLng: "en",
|
||||
|
@ -71,7 +71,7 @@ export default api.withTRPC(
|
||||
{
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: ["en", "zh-Hans"],
|
||||
locales: ["en", "pl", "zh-Hans"],
|
||||
localeDetection: false,
|
||||
},
|
||||
fallbackLng: "en",
|
||||
|
@ -14,6 +14,7 @@
|
||||
"settings.server.webServer.actions": "Actions",
|
||||
"settings.server.webServer.reload": "Reload",
|
||||
"settings.server.webServer.watchLogs": "Watch logs",
|
||||
"settings.server.webServer.updateServerIp": "Update Server IP",
|
||||
"settings.server.webServer.server.label": "Server",
|
||||
"settings.server.webServer.traefik.label": "Traefik",
|
||||
"settings.server.webServer.traefik.modifyEnv": "Modify Env",
|
||||
|
1
apps/dokploy/public/locales/pl/common.json
Normal file
1
apps/dokploy/public/locales/pl/common.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
44
apps/dokploy/public/locales/pl/settings.json
Normal file
44
apps/dokploy/public/locales/pl/settings.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"settings.common.save": "Zapisz",
|
||||
"settings.server.domain.title": "Domena",
|
||||
"settings.server.domain.description": "Dodaj domenę do aplikacji",
|
||||
"settings.server.domain.form.domain": "Domena",
|
||||
"settings.server.domain.form.letsEncryptEmail": "Email Let's Encrypt",
|
||||
"settings.server.domain.form.certificate.label": "Certyfikat",
|
||||
"settings.server.domain.form.certificate.placeholder": "Wybierz certyfikat",
|
||||
"settings.server.domain.form.certificateOptions.none": "Brak",
|
||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Domyślny)",
|
||||
|
||||
"settings.server.webServer.title": "Serwer",
|
||||
"settings.server.webServer.description": "Przeładuj lub wyczyść serwer",
|
||||
"settings.server.webServer.actions": "Akcje",
|
||||
"settings.server.webServer.reload": "Przeładuj",
|
||||
"settings.server.webServer.watchLogs": "Obserwuj logi",
|
||||
"settings.server.webServer.updateServerIp": "Zaktualizuj IP serwera",
|
||||
"settings.server.webServer.server.label": "Serwer",
|
||||
"settings.server.webServer.traefik.label": "Traefik",
|
||||
"settings.server.webServer.traefik.modifyEnv": "Zmodyfikuj środowisko",
|
||||
"settings.server.webServer.storage.label": "Przestrzeń",
|
||||
"settings.server.webServer.storage.cleanUnusedImages": "Wyczyść nieużywane obrazy",
|
||||
"settings.server.webServer.storage.cleanUnusedVolumes": "Wyczyść nieużywane wolumeny",
|
||||
"settings.server.webServer.storage.cleanStoppedContainers": "Wyczyść zatrzymane kontenery",
|
||||
"settings.server.webServer.storage.cleanDockerBuilder": "Wyczyść Docker Builder i System",
|
||||
"settings.server.webServer.storage.cleanMonitoring": "Wyczyść monitorowanie",
|
||||
"settings.server.webServer.storage.cleanAll": "Wyczyść wszystko",
|
||||
|
||||
"settings.profile.title": "Konto",
|
||||
"settings.profile.description": "Zmień szczegóły swojego profilu",
|
||||
"settings.profile.email": "Email",
|
||||
"settings.profile.password": "Hasło",
|
||||
"settings.profile.avatar": "Avatar",
|
||||
|
||||
"settings.appearance.title": "Wygląd",
|
||||
"settings.appearance.description": "Dostosuj motyw swojego pulpitu",
|
||||
"settings.appearance.theme": "Motyw",
|
||||
"settings.appearance.themeDescription": "Wybierz motyw swojego pulpitu",
|
||||
"settings.appearance.themes.light": "Jasny",
|
||||
"settings.appearance.themes.dark": "Ciemny",
|
||||
"settings.appearance.themes.system": "System",
|
||||
"settings.appearance.language": "Język",
|
||||
"settings.appearance.languageDescription": "Wybierz język swojego pulpitu"
|
||||
}
|
@ -4,6 +4,7 @@ import {
|
||||
apiCreateUserInvitation,
|
||||
apiFindOneToken,
|
||||
apiRemoveUser,
|
||||
apiUpdateAdmin,
|
||||
users,
|
||||
} from "@/server/db/schema";
|
||||
import {
|
||||
@ -13,6 +14,7 @@ import {
|
||||
findUserById,
|
||||
getUserByToken,
|
||||
removeUserByAuthId,
|
||||
updateAdmin,
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
@ -26,6 +28,18 @@ export const adminRouter = createTRPCRouter({
|
||||
...rest,
|
||||
};
|
||||
}),
|
||||
update: adminProcedure
|
||||
.input(apiUpdateAdmin)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
if (ctx.user.rol === "user") {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not allowed to update this admin",
|
||||
});
|
||||
}
|
||||
const { authId } = await findAdminById(ctx.user.adminId);
|
||||
return updateAdmin(authId, input);
|
||||
}),
|
||||
createUserInvitation: adminProcedure
|
||||
.input(apiCreateUserInvitation)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
findAdminById,
|
||||
findServerById,
|
||||
findServersByAdminId,
|
||||
getPublicIpWithFallback,
|
||||
haveActiveServices,
|
||||
removeDeploymentsByServerId,
|
||||
serverSetup,
|
||||
@ -181,4 +182,11 @@ export const serverRouter = createTRPCRouter({
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
publicIp: protectedProcedure.query(async ({ ctx }) => {
|
||||
if (IS_CLOUD) {
|
||||
return "";
|
||||
}
|
||||
const ip = await getPublicIpWithFallback();
|
||||
return ip;
|
||||
}),
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
const SUPPORTED_LOCALES = ["en", "zh-Hans"] as const;
|
||||
const SUPPORTED_LOCALES = ["en", "pl", "zh-Hans"] as const;
|
||||
|
||||
type Locale = (typeof SUPPORTED_LOCALES)[number];
|
||||
|
||||
|
@ -53,6 +53,8 @@ const createSchema = createInsertSchema(admins, {
|
||||
letsEncryptEmail: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiUpdateAdmin = createSchema.partial();
|
||||
|
||||
export const apiSaveSSHKey = createSchema
|
||||
.pick({
|
||||
sshPrivateKey: true,
|
||||
|
Loading…
Reference in New Issue
Block a user