diff --git a/apps/dokploy/components/dashboard/monitoring/free/container/compact-container-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/free/container/compact-container-monitoring.tsx new file mode 100644 index 000000000..0d62eb3b7 --- /dev/null +++ b/apps/dokploy/components/dashboard/monitoring/free/container/compact-container-monitoring.tsx @@ -0,0 +1,210 @@ +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Progress } from "@/components/ui/progress"; +import { api } from "@/utils/api"; +import { DockerCpuChart } from "./docker-cpu-chart"; +import { DockerMemoryChart } from "./docker-memory-chart"; +import { + type DockerStats, + type DockerStatsJSON, + convertMemoryToBytes, +} from "./show-free-container-monitoring"; + +const defaultData: DockerStats = { + cpu: { + value: "0%", + time: "", + }, + memory: { + value: { + used: 0, + total: 0, + }, + time: "", + }, + block: { + value: { + readMb: 0, + writeMb: 0, + }, + time: "", + }, + network: { + value: { + inputMb: 0, + outputMb: 0, + }, + time: "", + }, + disk: { + value: { diskTotal: 0, diskUsage: 0, diskUsedPercentage: 0, diskFree: 0 }, + time: "", + }, +}; + +interface Props { + appName: string; + appType?: "application" | "stack" | "docker-compose"; + label?: string; +} + +export const CompactContainerMonitoring = ({ + appName, + appType = "application", + label, +}: Props) => { + const { data } = api.application.readAppMonitoring.useQuery( + { appName }, + { + refetchOnWindowFocus: false, + enabled: !!appName, + }, + ); + const [accumulativeData, setAccumulativeData] = useState({ + cpu: [], + memory: [], + block: [], + network: [], + disk: [], + }); + const [currentData, setCurrentData] = useState(defaultData); + + useEffect(() => { + setCurrentData(defaultData); + setAccumulativeData({ + cpu: [], + memory: [], + block: [], + network: [], + disk: [], + }); + }, [appName]); + + useEffect(() => { + if (!data) return; + + setCurrentData({ + cpu: data.cpu[data.cpu.length - 1] ?? currentData.cpu, + memory: data.memory[data.memory.length - 1] ?? currentData.memory, + block: data.block[data.block.length - 1] ?? currentData.block, + network: data.network[data.network.length - 1] ?? currentData.network, + disk: data.disk[data.disk.length - 1] ?? currentData.disk, + }); + setAccumulativeData({ + block: data?.block || [], + cpu: data?.cpu || [], + disk: data?.disk || [], + memory: data?.memory || [], + network: data?.network || [], + }); + }, [data]); + + useEffect(() => { + if (!appName) return; + + const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + const wsUrl = `${protocol}//${window.location.host}/listen-docker-stats-monitoring?appName=${appName}&appType=${appType}`; + const ws = new WebSocket(wsUrl); + + ws.onmessage = (e) => { + const value = JSON.parse(e.data); + if (!value) return; + + const nextData = { + cpu: value.data.cpu ?? currentData.cpu, + memory: value.data.memory ?? currentData.memory, + block: value.data.block ?? currentData.block, + disk: value.data.disk ?? currentData.disk, + network: value.data.network ?? currentData.network, + }; + + setCurrentData(nextData); + + const MAX_DATA_POINTS = 300; + setAccumulativeData((prevData) => ({ + cpu: [...prevData.cpu, nextData.cpu].slice(-MAX_DATA_POINTS), + memory: [...prevData.memory, nextData.memory].slice(-MAX_DATA_POINTS), + block: [...prevData.block, nextData.block].slice(-MAX_DATA_POINTS), + network: [...prevData.network, nextData.network].slice( + -MAX_DATA_POINTS, + ), + disk: [...prevData.disk, nextData.disk].slice(-MAX_DATA_POINTS), + })); + }; + + ws.onclose = (e) => { + if (e.reason) { + toast.error(e.reason); + } + }; + + return () => ws.close(); + }, [appName, appType]); + + return ( +
+ {label && ( +
+

{label}

+

+ Watch the usage of your server in the current app +

+
+ )} + +
+ + + CPU Usage + + +
+ + Used: {String(currentData.cpu.value ?? "0%")} + + + +
+
+
+ + + Memory Usage + + +
+ + {`Used: ${currentData.memory.value.used} / Limit: ${currentData.memory.value.total} `} + + + +
+
+
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/compact-paid-container-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/compact-paid-container-monitoring.tsx new file mode 100644 index 000000000..94d71c6ea --- /dev/null +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/compact-paid-container-monitoring.tsx @@ -0,0 +1,105 @@ +import { Loader2 } from "lucide-react"; +import { useEffect, useState } from "react"; +import { api } from "@/utils/api"; +import { ContainerCPUChart } from "./container-cpu-chart"; +import { ContainerMemoryChart } from "./container-memory-chart"; + +interface ContainerMetric { + timestamp: string; + CPU: number; + Memory: { + percentage: number; + used: number; + total: number; + unit: string; + usedUnit: string; + totalUnit: string; + }; + Network: { + input: number; + output: number; + inputUnit: string; + outputUnit: string; + }; + BlockIO: { + read: number; + write: number; + readUnit: string; + writeUnit: string; + }; + Container: string; + ID: string; + Name: string; +} + +interface Props { + appName: string; + baseUrl: string; + token: string; + label?: string; +} + +export const CompactPaidContainerMonitoring = ({ + appName, + baseUrl, + token, + label, +}: Props) => { + const [historicalData, setHistoricalData] = useState([]); + + const { data, isLoading, error } = api.user.getContainerMetrics.useQuery( + { + url: baseUrl, + token, + dataPoints: "200", + appName, + }, + { + refetchInterval: 10000, + enabled: !!appName && !!baseUrl && !!token, + }, + ); + + useEffect(() => { + if (!data) return; + // @ts-ignore + setHistoricalData(data); + }, [data]); + + if (isLoading && historicalData.length === 0) { + return ( +
+ + Loading metrics... +
+ ); + } + + if (error) { + return ( +
+ Unable to fetch metrics for {label || appName}. +
+ ); + } + + return ( +
+ {label && ( +
+

{label}

+

+ Watch the usage of your server in the current app +

+
+ )} + + {historicalData.length > 0 && ( +
+ + +
+ )} +
+ ); +};