fix: copy Dokploy server IP when clicking server badge (#4390)

* fix: copy Dokploy server IP when clicking server badge

When a service runs on the local Dokploy server (no remote server),
clicking the server badge did nothing because `data.server` is null.
Now falls back to the server IP from settings so the badge always
copies an IP address.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(copy-ip): implement IP address copying functionality across database service components

- Added the ability to copy the server IP address to the clipboard when clicking the server badge in various database service components (Libsql, MariaDB, MongoDB, MySQL, PostgreSQL, Redis).
- Integrated the `copy-to-clipboard` library and `sonner` for user feedback upon successful copy action.
- Ensured fallback to the server IP from settings when the service data is not available, enhancing user experience and functionality.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
This commit is contained in:
Volodymyr Kravchuk
2026-05-13 10:03:29 +03:00
committed by GitHub
parent a50f958a6f
commit 8d88a34a64
8 changed files with 74 additions and 4 deletions
@@ -93,6 +93,7 @@ const Service = (
);
const { data: isCloud } = api.settings.isCloud.useQuery();
const { data: serverIp } = api.settings.getIp.useQuery();
const { data: auth } = api.user.get.useQuery();
const { data: permissions } = api.user.getPermissions.useQuery();
@@ -147,8 +148,9 @@ const Service = (
<Badge
className="cursor-pointer"
onClick={() => {
if (data?.server?.ipAddress) {
copy(data.server.ipAddress);
const ip = data?.server?.ipAddress || serverIp;
if (ip) {
copy(ip);
toast.success("IP Address Copied!");
}
}}
@@ -85,6 +85,7 @@ const Service = (
const { data: auth } = api.user.get.useQuery();
const { data: permissions } = api.user.getPermissions.useQuery();
const { data: isCloud } = api.settings.isCloud.useQuery();
const { data: serverIp } = api.settings.getIp.useQuery();
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
});
@@ -134,8 +135,9 @@ const Service = (
<Badge
className="cursor-pointer"
onClick={() => {
if (data?.server?.ipAddress) {
copy(data.server.ipAddress);
const ip = data?.server?.ipAddress || serverIp;
if (ip) {
copy(ip);
toast.success("IP Address Copied!");
}
}}
@@ -1,3 +1,4 @@
import copy from "copy-to-clipboard";
import { validateRequest } from "@dokploy/server/lib/auth";
import { createServerSideHelpers } from "@trpc/react-query/server";
import { HelpCircle, ServerOff } from "lucide-react";
@@ -10,6 +11,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { type ReactElement, useState } from "react";
import superjson from "superjson";
import { toast } from "sonner";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
@@ -61,6 +63,7 @@ const Libsql = (
const { data: auth } = api.user.get.useQuery();
const { data: isCloud } = api.settings.isCloud.useQuery();
const { data: serverIp } = api.settings.getIp.useQuery();
return (
<div className="pb-10">
@@ -99,6 +102,14 @@ const Libsql = (
<div className="flex flex-col h-fit w-fit gap-2">
<div className="flex flex-row h-fit w-fit gap-2">
<Badge
className="cursor-pointer"
onClick={() => {
const ip = data?.server?.ipAddress || serverIp;
if (ip) {
copy(ip);
toast.success("IP Address Copied!");
}
}}
variant={
!data?.serverId
? "default"
@@ -1,3 +1,4 @@
import copy from "copy-to-clipboard";
import { validateRequest } from "@dokploy/server/lib/auth";
import { createServerSideHelpers } from "@trpc/react-query/server";
import { HelpCircle, ServerOff } from "lucide-react";
@@ -10,6 +11,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { type ReactElement, useState } from "react";
import superjson from "superjson";
import { toast } from "sonner";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
@@ -63,6 +65,7 @@ const Mariadb = (
const { data: permissions } = api.user.getPermissions.useQuery();
const { data: isCloud } = api.settings.isCloud.useQuery();
const { data: serverIp } = api.settings.getIp.useQuery();
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
@@ -111,6 +114,14 @@ const Mariadb = (
<div className="flex flex-col h-fit w-fit gap-2">
<div className="flex flex-row h-fit w-fit gap-2">
<Badge
className="cursor-pointer"
onClick={() => {
const ip = data?.server?.ipAddress || serverIp;
if (ip) {
copy(ip);
toast.success("IP Address Copied!");
}
}}
variant={
!data?.serverId
? "default"
@@ -1,3 +1,4 @@
import copy from "copy-to-clipboard";
import { validateRequest } from "@dokploy/server/lib/auth";
import { createServerSideHelpers } from "@trpc/react-query/server";
import { HelpCircle, ServerOff } from "lucide-react";
@@ -10,6 +11,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { type ReactElement, useState } from "react";
import superjson from "superjson";
import { toast } from "sonner";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
@@ -63,6 +65,7 @@ const Mongo = (
const { data: permissions } = api.user.getPermissions.useQuery();
const { data: isCloud } = api.settings.isCloud.useQuery();
const { data: serverIp } = api.settings.getIp.useQuery();
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
});
@@ -110,6 +113,14 @@ const Mongo = (
<div className="flex flex-col h-fit w-fit gap-2">
<div className="flex flex-row h-fit w-fit gap-2">
<Badge
className="cursor-pointer"
onClick={() => {
const ip = data?.server?.ipAddress || serverIp;
if (ip) {
copy(ip);
toast.success("IP Address Copied!");
}
}}
variant={
!data?.serverId
? "default"
@@ -1,5 +1,6 @@
import { validateRequest } from "@dokploy/server/lib/auth";
import { createServerSideHelpers } from "@trpc/react-query/server";
import copy from "copy-to-clipboard";
import { HelpCircle, ServerOff } from "lucide-react";
import type {
GetServerSidePropsContext,
@@ -10,6 +11,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { type ReactElement, useState } from "react";
import superjson from "superjson";
import { toast } from "sonner";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
@@ -62,6 +64,7 @@ const MySql = (
const { data: permissions } = api.user.getPermissions.useQuery();
const { data: isCloud } = api.settings.isCloud.useQuery();
const { data: serverIp } = api.settings.getIp.useQuery();
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
});
@@ -110,6 +113,14 @@ const MySql = (
<div className="flex flex-col h-fit w-fit gap-2">
<div className="flex flex-row h-fit w-fit gap-2">
<Badge
className="cursor-pointer"
onClick={() => {
const ip = data?.server?.ipAddress || serverIp;
if (ip) {
copy(ip);
toast.success("IP Address Copied!");
}
}}
variant={
!data?.serverId
? "default"
@@ -1,3 +1,4 @@
import copy from "copy-to-clipboard";
import { validateRequest } from "@dokploy/server/lib/auth";
import { createServerSideHelpers } from "@trpc/react-query/server";
import { HelpCircle, ServerOff } from "lucide-react";
@@ -10,6 +11,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { type ReactElement, useState } from "react";
import superjson from "superjson";
import { toast } from "sonner";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
@@ -62,6 +64,7 @@ const Postgresql = (
const { data: permissions } = api.user.getPermissions.useQuery();
const { data: isCloud } = api.settings.isCloud.useQuery();
const { data: serverIp } = api.settings.getIp.useQuery();
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
});
@@ -109,6 +112,14 @@ const Postgresql = (
<div className="flex flex-col h-fit w-fit gap-2">
<div className="flex flex-row h-fit w-fit gap-2">
<Badge
className="cursor-pointer"
onClick={() => {
const ip = data?.server?.ipAddress || serverIp;
if (ip) {
copy(ip);
toast.success("IP Address Copied!");
}
}}
variant={
!data?.serverId
? "default"
@@ -1,3 +1,4 @@
import copy from "copy-to-clipboard";
import { validateRequest } from "@dokploy/server/lib/auth";
import { createServerSideHelpers } from "@trpc/react-query/server";
import { HelpCircle, ServerOff } from "lucide-react";
@@ -10,6 +11,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { type ReactElement, useState } from "react";
import superjson from "superjson";
import { toast } from "sonner";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
@@ -62,6 +64,7 @@ const Redis = (
const { data: permissions } = api.user.getPermissions.useQuery();
const { data: isCloud } = api.settings.isCloud.useQuery();
const { data: serverIp } = api.settings.getIp.useQuery();
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
});
@@ -109,6 +112,14 @@ const Redis = (
<div className="flex flex-col h-fit w-fit gap-2">
<div className="flex flex-row h-fit w-fit gap-2">
<Badge
className="cursor-pointer"
onClick={() => {
const ip = data?.server?.ipAddress || serverIp;
if (ip) {
copy(ip);
toast.success("IP Address Copied!");
}
}}
variant={
!data?.serverId
? "default"