diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx index c49be0c4..194bf720 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx @@ -12,6 +12,7 @@ import { ContainerBlockChart } from "./container-block-chart"; import { ContainerCPUChart } from "./container-cpu-chart"; import { ContainerMemoryChart } from "./container-memory-chart"; import { ContainerNetworkChart } from "./container-network-chart"; +import { api } from "@/utils/api"; const REFRESH_INTERVALS = { "5000": "5 Seconds", @@ -70,84 +71,36 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => { const [metrics, setMetrics] = useState( {} as ContainerMetric, ); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); const [dataPoints, setDataPoints] = useState("50"); const [refreshInterval, setRefreshInterval] = useState("5000"); - const fetchMetrics = async () => { - try { - const url = new URL(`${baseUrl}/metrics/containers`); - - // if (dataPoints !== "all") { - url.searchParams.append("limit", dataPoints); - // } - - if (!appName) { - throw new Error( - [ - "No Application Selected:", - "", - "Make Sure to select an application to monitor.", - ].join("\n"), - ); - } - - url.searchParams.append("appName", appName); - - const response = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - if (!response.ok) { - throw new Error( - `Error ${response.status}: ${response.statusText}. Please verify that the application "${appName}" is running and this service is included in the monitoring configuration.`, - ); - } - - const data = await response.json(); - if (!Array.isArray(data) || data.length === 0) { - throw new Error( - [ - `No monitoring data available for "${appName}". This could be because:`, - "", - "1. The container was recently started - wait a few minutes for data to be collected", - "2. The container is not running - verify its status", - "3. The service is not included in your monitoring configuration", - ].join("\n"), - ); - } - - setHistoricalData(data); - setMetrics(data[data.length - 1]); - setError(null); - } catch (err) { - setError( - err instanceof Error - ? err.message - : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly.", - ); - } finally { - setIsLoading(false); - } - }; + const { + data, + isLoading, + error: queryError, + } = api.admin.getContainerMetrics.useQuery( + { + url: baseUrl, + token, + dataPoints, + appName, + }, + { + refetchInterval: + dataPoints === "all" ? undefined : Number.parseInt(refreshInterval), + enabled: !!appName, + }, + ); useEffect(() => { - fetchMetrics(); + if (!data) return; - if (dataPoints === "all") { - return; - } - - const interval = setInterval(() => { - fetchMetrics(); - }, Number(refreshInterval)); - - return () => clearInterval(interval); - }, [dataPoints, appName, token, refreshInterval]); + // @ts-ignore + setHistoricalData(data); + // @ts-ignore + setMetrics(data[data.length - 1]); + }, [data]); if (isLoading) { return ( @@ -157,7 +110,7 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => { ); } - if (error) { + if (queryError) { return (
@@ -166,7 +119,9 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => { {appName}

- {error} + {queryError instanceof Error + ? queryError.message + : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}

URL: {baseUrl}

@@ -238,11 +193,11 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {

Memory Usage

- {metrics.Memory.percentage}% + {metrics?.Memory?.percentage}%

- {metrics.Memory.used} {metrics.Memory.unit} / {metrics.Memory.total}{" "} - {metrics.Memory.unit} + {metrics?.Memory?.used} {metrics?.Memory?.unit} /{" "} + {metrics?.Memory?.total} {metrics?.Memory?.unit}

@@ -252,8 +207,8 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {

Network I/O

- {metrics.Network.input} {metrics.Network.inputUnit} /{" "} - {metrics.Network.output} {metrics.Network.outputUnit} + {metrics?.Network?.input} {metrics?.Network?.inputUnit} /{" "} + {metrics?.Network?.output} {metrics?.Network?.outputUnit}

@@ -263,8 +218,8 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {

Block I/O

- {metrics.BlockIO.read} {metrics.BlockIO.readUnit} /{" "} - {metrics.BlockIO.write} {metrics.BlockIO.writeUnit} + {metrics?.BlockIO?.read} {metrics?.BlockIO?.readUnit} /{" "} + {metrics?.BlockIO?.write} {metrics?.BlockIO?.writeUnit}

diff --git a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx index 90148dfa..096df998 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx @@ -12,6 +12,7 @@ import { CPUChart } from "./cpu-chart"; import { DiskChart } from "./disk-chart"; import { MemoryChart } from "./memory-chart"; import { NetworkChart } from "./network-chart"; +import { api } from "@/utils/api"; const REFRESH_INTERVALS = { "5000": "5 Seconds", @@ -64,76 +65,56 @@ export const ShowPaidMonitoring = ({ }: Props) => { const [historicalData, setHistoricalData] = useState([]); const [metrics, setMetrics] = useState({} as SystemMetrics); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); const [dataPoints, setDataPoints] = useState("50"); const [refreshInterval, setRefreshInterval] = useState("5000"); - const fetchMetrics = async () => { - try { - const url = new URL(BASE_URL); - url.searchParams.append("limit", dataPoints); - const response = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + const { + data, + isLoading, + error: queryError, + } = api.admin.getServerMetrics.useQuery( + { + url: BASE_URL, + token, + dataPoints, + }, + { + refetchInterval: + dataPoints === "all" ? undefined : Number.parseInt(refreshInterval), + enabled: true, + }, + ); - if (!response.ok) { - throw new Error( - `Error ${response.status}: ${response.statusText}. Ensure the container is running and this service is included in the monitoring configuration.`, - ); - } + useEffect(() => { + if (!data) return; - const data = await response.json(); - if (!Array.isArray(data) || data.length === 0) { - throw new Error( - [ - "No monitoring data available. This could be because:", - "", - "1. You don't have setup the monitoring service, you can do in web server section.", - "2. If you already have setup the monitoring service, wait a few minutes and refresh the page.", - ].join("\n"), - ); - } + const formattedData = data.map((metric: SystemMetrics) => ({ + timestamp: metric.timestamp, + cpu: Number.parseFloat(metric.cpu), + cpuModel: metric.cpuModel, + cpuCores: metric.cpuCores, + cpuPhysicalCores: metric.cpuPhysicalCores, + cpuSpeed: metric.cpuSpeed, + os: metric.os, + distro: metric.distro, + kernel: metric.kernel, + arch: metric.arch, + memUsed: Number.parseFloat(metric.memUsed), + memUsedGB: Number.parseFloat(metric.memUsedGB), + memTotal: Number.parseFloat(metric.memTotal), + networkIn: Number.parseFloat(metric.networkIn), + networkOut: Number.parseFloat(metric.networkOut), + diskUsed: Number.parseFloat(metric.diskUsed), + totalDisk: Number.parseFloat(metric.totalDisk), + uptime: metric.uptime, + })); - const formattedData = data.map((metric: SystemMetrics) => ({ - timestamp: metric.timestamp, - cpu: Number.parseFloat(metric.cpu), - cpuModel: metric.cpuModel, - cpuCores: metric.cpuCores, - cpuPhysicalCores: metric.cpuPhysicalCores, - cpuSpeed: metric.cpuSpeed, - os: metric.os, - distro: metric.distro, - kernel: metric.kernel, - arch: metric.arch, - memUsed: Number.parseFloat(metric.memUsed), - memUsedGB: Number.parseFloat(metric.memUsedGB), - memTotal: Number.parseFloat(metric.memTotal), - networkIn: Number.parseFloat(metric.networkIn), - networkOut: Number.parseFloat(metric.networkOut), - diskUsed: Number.parseFloat(metric.diskUsed), - totalDisk: Number.parseFloat(metric.totalDisk), - uptime: metric.uptime, - })); - - // @ts-ignore - setHistoricalData(formattedData); - // @ts-ignore - setMetrics(formattedData[formattedData.length - 1] || {}); - setError(null); - } catch (err) { - setError( - err instanceof Error - ? err.message - : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly.", - ); - } finally { - setIsLoading(false); - } - }; + // @ts-ignore + setHistoricalData(formattedData); + // @ts-ignore + setMetrics(formattedData[formattedData.length - 1] || {}); + }, [data]); const formatUptime = (seconds: number): string => { const days = Math.floor(seconds / (24 * 60 * 60)); @@ -143,20 +124,6 @@ export const ShowPaidMonitoring = ({ return `${days}d ${hours}h ${minutes}m`; }; - useEffect(() => { - fetchMetrics(); - - if (dataPoints === "all") { - return; - } - - const interval = setInterval(() => { - fetchMetrics(); - }, Number(refreshInterval)); - - return () => clearInterval(interval); - }, [dataPoints, token, refreshInterval]); - if (isLoading) { return (
@@ -165,7 +132,7 @@ export const ShowPaidMonitoring = ({ ); } - if (error) { + if (queryError) { return (
@@ -173,7 +140,9 @@ export const ShowPaidMonitoring = ({ Error fetching metrics{" "}

- {error} + {queryError instanceof Error + ? queryError.message + : "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}

URL: {BASE_URL}

diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx index 781854f8..9d3ffe4f 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx @@ -399,6 +399,8 @@ export async function getServerSideProps( applicationId: params?.applicationId, }); + await helpers.settings.isCloud.prefetch(); + return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx index b9ed1d8c..c6840214 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx @@ -394,7 +394,7 @@ export async function getServerSideProps( await helpers.compose.one.fetch({ composeId: params?.composeId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx index d441b08c..a2ee9051 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx @@ -343,7 +343,7 @@ export async function getServerSideProps( await helpers.mariadb.one.fetch({ mariadbId: params?.mariadbId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx index 7fdb1e77..4f3947c2 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx @@ -345,7 +345,7 @@ export async function getServerSideProps( await helpers.mongo.one.fetch({ mongoId: params?.mongoId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx index ba4efecc..baf7e6f8 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx @@ -350,7 +350,7 @@ export async function getServerSideProps( await helpers.mysql.one.fetch({ mysqlId: params?.mysqlId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx index 047d1c57..e3fd8b44 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx @@ -346,7 +346,7 @@ export async function getServerSideProps( await helpers.postgres.one.fetch({ postgresId: params?.postgresId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx index 1b041640..3421e759 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx @@ -337,7 +337,7 @@ export async function getServerSideProps( await helpers.redis.one.fetch({ redisId: params?.redisId, }); - + await helpers.settings.isCloud.prefetch(); return { props: { trpcState: helpers.dehydrate(), diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts index f10bc759..ab1cd8e8 100644 --- a/apps/dokploy/server/api/routers/admin.ts +++ b/apps/dokploy/server/api/routers/admin.ts @@ -28,6 +28,7 @@ import { protectedProcedure, publicProcedure, } from "../trpc"; +import { z } from "zod"; export const adminRouter = createTRPCRouter({ one: adminProcedure.query(async ({ ctx }) => { @@ -169,4 +170,121 @@ export const adminRouter = createTRPCRouter({ metricsConfig: admin?.metricsConfig, }; }), + + getServerMetrics: protectedProcedure + .input( + z.object({ + url: z.string(), + token: z.string(), + dataPoints: z.string(), + }), + ) + .query(async ({ ctx, input }) => { + try { + const url = new URL(input.url); + url.searchParams.append("limit", input.dataPoints); + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${input.token}`, + }, + }); + if (!response.ok) { + throw new Error( + `Error ${response.status}: ${response.statusText}. Ensure the container is running and this service is included in the monitoring configuration.`, + ); + } + + const data = await response.json(); + if (!Array.isArray(data) || data.length === 0) { + throw new Error( + [ + "No monitoring data available. This could be because:", + "", + "1. You don't have setup the monitoring service, you can do in web server section.", + "2. If you already have setup the monitoring service, wait a few minutes and refresh the page.", + ].join("\n"), + ); + } + return data as { + cpu: string; + cpuModel: string; + cpuCores: number; + cpuPhysicalCores: number; + cpuSpeed: number; + os: string; + distro: string; + kernel: string; + arch: string; + memUsed: string; + memUsedGB: string; + memTotal: string; + uptime: number; + diskUsed: string; + totalDisk: string; + networkIn: string; + networkOut: string; + timestamp: string; + }[]; + } catch (error) { + throw error; + } + }), + getContainerMetrics: protectedProcedure + .input( + z.object({ + url: z.string(), + token: z.string(), + appName: z.string(), + dataPoints: z.string(), + }), + ) + .query(async ({ ctx, input }) => { + try { + if (!input.appName) { + throw new Error( + [ + "No Application Selected:", + "", + "Make Sure to select an application to monitor.", + ].join("\n"), + ); + } + const url = new URL(`${input.url}/metrics/containers`); + url.searchParams.append("limit", input.dataPoints); + url.searchParams.append("appName", input.appName); + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${input.token}`, + }, + }); + if (!response.ok) { + throw new Error( + `Error ${response.status}: ${response.statusText}. Please verify that the application "${input.appName}" is running and this service is included in the monitoring configuration.`, + ); + } + + const data = await response.json(); + if (!Array.isArray(data) || data.length === 0) { + throw new Error( + [ + `No monitoring data available for "${input.appName}". This could be because:`, + "", + "1. The container was recently started - wait a few minutes for data to be collected", + "2. The container is not running - verify its status", + "3. The service is not included in your monitoring configuration", + ].join("\n"), + ); + } + return data as { + containerId: string; + containerName: string; + containerImage: string; + containerLabels: string; + containerCommand: string; + containerCreated: string; + }[]; + } catch (error) { + throw error; + } + }), });