-
-
-
CPU
-
- Used: {currentData.cpu.value.toFixed(2)}%
-
-
-
-
-
-
Memory
-
- {`Used: ${(currentData.memory.value.used / 1024 ** 3).toFixed(2)} GB / Limit: ${(currentData.memory.value.total / 1024 ** 3).toFixed(2)} GB`}
-
-
-
-
- {appName === "dokploy" && (
-
-
Space
-
- {`Used: ${currentData.disk.value.diskUsage} GB / Limit: ${currentData.disk.value.diskTotal} GB`}
-
-
-
-
- )}
-
- Block I/O
-
- {`Read: ${currentData.block.value.readMb.toFixed(
- 2,
- )} MB / Write: ${currentData.block.value.writeMb.toFixed(
- 3,
- )} MB`}
-
-
-
-
- Network
-
- {`In MB: ${currentData.network.value.inputMb.toFixed(
- 2,
- )} MB / Out MB: ${currentData.network.value.outputMb.toFixed(
- 2,
- )} MB`}
-
-
-
-
+
+ {/* Header con selector de puntos de datos */}
+
+
System Monitoring
+
+ Data points:
+
+
+
+
+ {/* Stats Cards */}
+
+
+
+
+
Uptime
+
+
+ {formatUptime(metrics.uptime || 0)}
+
+
+
+
+
+
+
CPU Usage
+
+
{metrics.cpu}%
+
+
+
+
+
+
Memory Usage
+
+
+ {metrics.memUsedGB} GB / {metrics.memTotal} GB
+
+
+
+
+
+
+
Disk Usage
+
+
{metrics.diskUsed}%
+
+
+
+ {/* System Information */}
+
+
System Information
+
+
+
CPU
+
{metrics.cpuModel}
+
+ {metrics.cpuPhysicalCores} Physical Cores ({metrics.cpuCores}{" "}
+ Threads) @ {metrics.cpuSpeed}GHz
+
+
+
+
+ Operating System
+
+
{metrics.distro}
+
+ Kernel: {metrics.kernel} ({metrics.arch})
+
-
-
+
+
+
+ {/* Charts Grid */}
+
+
+
+
+
+
);
};
diff --git a/apps/dokploy/components/dashboard/monitoring/show-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/show-monitoring.tsx
index b9be9a1b2..6f248376e 100644
--- a/apps/dokploy/components/dashboard/monitoring/show-monitoring.tsx
+++ b/apps/dokploy/components/dashboard/monitoring/show-monitoring.tsx
@@ -81,7 +81,7 @@ export const ShowMonitoring = ({
const data = await response.json();
if (!Array.isArray(data) || data.length === 0) {
- throw new Error("No hay datos disponibles");
+ throw new Error("No data available");
}
const formattedData = data.map((metric: SystemMetrics) => ({
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 c8ffd0367..3eceb3f62 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx
@@ -14,6 +14,7 @@ import { ShowGeneralApplication } from "@/components/dashboard/application/gener
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { ShowPreviewDeployments } from "@/components/dashboard/application/preview-deployments/show-preview-deployments";
import { UpdateApplication } from "@/components/dashboard/application/update-application";
+import { ContainerMonitoring } from "@/components/dashboard/monitoring/container/show";
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
import { ProjectLayout } from "@/components/layouts/project-layout";
import { StatusTooltip } from "@/components/shared/status-tooltip";
@@ -244,7 +245,10 @@ const Service = (
{!data?.serverId && (
-
+
)}
diff --git a/apps/monitoring/src/index.ts b/apps/monitoring/src/index.ts
index 2229b0714..215189695 100644
--- a/apps/monitoring/src/index.ts
+++ b/apps/monitoring/src/index.ts
@@ -3,9 +3,10 @@ import { Hono } from "hono";
import { cors } from "hono/cors";
import { logServerMetrics } from "./monitoring/server.js";
import { config } from "dotenv";
-import { serverLogFile } from "./constants.js";
+import { serverLogFile, containerLogFile } from "./constants.js";
import { processMetricsFromFile } from "./utils.js";
import { logContainerMetrics } from "./monitoring/containers.js";
+import { existsSync } from "fs";
config();
const TOKEN = process.env.TOKEN || "default-token";
@@ -63,20 +64,30 @@ app.get("/metrics", async (c) => {
}
});
-// app.get("/metrics/containers", async (c) => {
-// try {
-// const metrics = await processMetricsFromFile(containerLogFile, {
-// start: c.req.query("start"),
-// end: c.req.query("end"),
-// limit: Number(c.req.query("limit")) || undefined,
-// });
-
-// return c.json(metrics);
-// } catch (error) {
-// console.error("Error reading metrics:", error);
-// return c.json({ error: "Error reading metrics" }, 500);
-// }
-// });
+app.get("/metrics/containers", async (c) => {
+ try {
+ const appName = c.req.query("appName");
+ if (!appName) {
+ return c.json({ error: "No appName provided" }, 400);
+ }
+
+ const logPath = `${containerLogFile}/${appName}.log`;
+
+ if (existsSync(logPath) === false) {
+ return c.json([]);
+ }
+ const metrics = await processMetricsFromFile(logPath, {
+ start: c.req.query("start"),
+ end: c.req.query("end"),
+ limit: Number(c.req.query("limit")) || undefined,
+ });
+
+ return c.json(metrics);
+ } catch (error) {
+ console.error("Error reading metrics:", error);
+ return c.json({ error: "Error reading metrics" }, 500);
+ }
+});
app.get("/health", (c) => {
return c.text("OK");
diff --git a/apps/monitoring/src/monitoring/containers.ts b/apps/monitoring/src/monitoring/containers.ts
index f0eb348bc..c97d0ee52 100644
--- a/apps/monitoring/src/monitoring/containers.ts
+++ b/apps/monitoring/src/monitoring/containers.ts
@@ -38,6 +38,21 @@ function getServiceName(containerName: string): string {
export const logContainerMetrics = () => {
console.log("Initialized container metrics");
+
+ // Mantener un handle del archivo abierto para cada contenedor
+ const fileHandles = new Map
();
+
+ const cleanup = async () => {
+ for (const [_, handle] of fileHandles) {
+ await handle.close();
+ }
+ fileHandles.clear();
+ };
+
+ // Asegurar que cerramos los archivos al terminar
+ process.on("SIGTERM", cleanup);
+ process.on("SIGINT", cleanup);
+
setInterval(async () => {
try {
const { stdout } = await execAsync(
@@ -52,20 +67,34 @@ export const logContainerMetrics = () => {
}
for (const container of containers) {
- const serviceName = getServiceName(container.Name);
- const logLine = `${JSON.stringify({
- timestamp: new Date().toISOString(),
- ...container,
- })}\n`;
+ try {
+ const serviceName = getServiceName(container.Name);
+ const containerPath = join(containerLogFile, `${serviceName}.log`);
+
+ // Obtener o crear el handle del archivo
+ let fileHandle = fileHandles.get(serviceName);
+ if (!fileHandle) {
+ fileHandle = await fs.promises.open(containerPath, "a");
+ fileHandles.set(serviceName, fileHandle);
+ }
- const containerPath = join(containerLogFile, `${serviceName}.log`);
+ const logLine = `${JSON.stringify({
+ timestamp: new Date().toISOString(),
+ ...container,
+ })}\n`;
- fs.appendFile(containerPath, logLine, (err) => {
- if (err) console.error("Error writing container metrics:", err);
- });
+ await fileHandle.write(logLine);
+ } catch (error) {
+ console.error(
+ `Error writing metrics for container ${container.Name}:`,
+ error,
+ );
+ }
}
} catch (error) {
console.error("Error getting containers:", error);
}
}, REFRESH_RATE_CONTAINER);
+
+ return cleanup;
};
diff --git a/apps/monitoring/src/utils.ts b/apps/monitoring/src/utils.ts
index 4596512e6..ebf0414b9 100644
--- a/apps/monitoring/src/utils.ts
+++ b/apps/monitoring/src/utils.ts
@@ -15,13 +15,17 @@ export function parseLog(logContent: string) {
if (!logContent.trim()) return [];
const lines = logContent.trim().split("\n");
- return lines.map((line) => {
- try {
- return JSON.parse(line);
- } catch {
- return { raw: line };
- }
- });
+ return lines
+ .map((line) => {
+ try {
+ return JSON.parse(line);
+ } catch (error) {
+ console.error(`Error parsing log line: ${error}`);
+ console.error(`Problematic line: ${line}`);
+ return null;
+ }
+ })
+ .filter((entry): entry is Record => entry !== null);
}
/**
@@ -92,13 +96,17 @@ async function readLastNLines(filePath: string, limit: number) {
const lines = content.split("\n").filter((line) => line.trim());
const lastLines = lines.slice(-limit);
- return lastLines.map((line) => {
- try {
- return JSON.parse(line);
- } catch {
- return { raw: line };
- }
- });
+ return lastLines
+ .map((line) => {
+ try {
+ return JSON.parse(line);
+ } catch (error) {
+ console.error(`Error parsing log line: ${error}`);
+ console.error(`Problematic line: ${line}`);
+ return null;
+ }
+ })
+ .filter((entry): entry is Record => entry !== null);
} finally {
await fd.close();
}