mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
refactor: update docker stats
This commit is contained in:
@@ -90,9 +90,11 @@ const CustomTooltip = ({ active, payload }: CustomTooltipProps) => {
|
|||||||
if (active && payload && payload.length && payload[0]) {
|
if (active && payload && payload.length && payload[0]) {
|
||||||
return (
|
return (
|
||||||
<div className="custom-tooltip bg-background p-2 shadow-lg rounded-md text-primary border">
|
<div className="custom-tooltip bg-background p-2 shadow-lg rounded-md text-primary border">
|
||||||
<p>{`Date: ${format(new Date(payload[0].payload.time), "PPpp")}`}</p>
|
{payload[0].payload.time && (
|
||||||
<p>{`Read ${payload[0].payload.readMb.toFixed(2)} MB`}</p>
|
<p>{`Date: ${format(new Date(payload[0].payload.time), "PPpp")}`}</p>
|
||||||
<p>{`Write: ${payload[0].payload.writeMb.toFixed(3)} MB`}</p>
|
)}
|
||||||
|
<p>{`Read ${payload[0].payload.readMb} `}</p>
|
||||||
|
<p>{`Write: ${payload[0].payload.writeMb} `}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const DockerCpuChart = ({ acummulativeData }: Props) => {
|
|||||||
return {
|
return {
|
||||||
name: `Point ${index + 1}`,
|
name: `Point ${index + 1}`,
|
||||||
time: item.time,
|
time: item.time,
|
||||||
usage: item.value.toFixed(2),
|
usage: item.value.toString().split("%")[0],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
@@ -75,7 +75,9 @@ const CustomTooltip = ({ active, payload }: CustomTooltipProps) => {
|
|||||||
if (active && payload && payload.length && payload[0]) {
|
if (active && payload && payload.length && payload[0]) {
|
||||||
return (
|
return (
|
||||||
<div className="custom-tooltip bg-background p-2 shadow-lg rounded-md text-primary border">
|
<div className="custom-tooltip bg-background p-2 shadow-lg rounded-md text-primary border">
|
||||||
<p>{`Date: ${format(new Date(payload[0].payload.time), "PPpp")}`}</p>
|
{payload[0].payload.time && (
|
||||||
|
<p>{`Date: ${format(new Date(payload[0].payload.time), "PPpp")}`}</p>
|
||||||
|
)}
|
||||||
<p>{`CPU Usage: ${payload[0].payload.usage}%`}</p>
|
<p>{`CPU Usage: ${payload[0].payload.usage}%`}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
YAxis,
|
YAxis,
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import type { DockerStatsJSON } from "./show";
|
import type { DockerStatsJSON } from "./show";
|
||||||
|
import { convertMemoryToBytes } from "./show";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
acummulativeData: DockerStatsJSON["memory"];
|
acummulativeData: DockerStatsJSON["memory"];
|
||||||
@@ -23,7 +24,8 @@ export const DockerMemoryChart = ({
|
|||||||
return {
|
return {
|
||||||
time: item.time,
|
time: item.time,
|
||||||
name: `Point ${index + 1}`,
|
name: `Point ${index + 1}`,
|
||||||
usage: (item.value.used / 1024 ** 3).toFixed(2),
|
// @ts-ignore
|
||||||
|
usage: (convertMemoryToBytes(item.value.used) / 1024 ** 3).toFixed(2),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
@@ -75,10 +77,13 @@ interface CustomTooltipProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CustomTooltip = ({ active, payload }: CustomTooltipProps) => {
|
const CustomTooltip = ({ active, payload }: CustomTooltipProps) => {
|
||||||
if (active && payload && payload.length && payload[0]) {
|
if (active && payload && payload.length && payload[0] && payload[0].payload) {
|
||||||
return (
|
return (
|
||||||
<div className="custom-tooltip bg-background p-2 shadow-lg rounded-md text-primary border">
|
<div className="custom-tooltip bg-background p-2 shadow-lg rounded-md text-primary border">
|
||||||
<p>{`Date: ${format(new Date(payload[0].payload.time), "PPpp")}`}</p>
|
{payload[0].payload.time && (
|
||||||
|
<p>{`Date: ${format(new Date(payload[0].payload.time), "PPpp")}`}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<p>{`Memory usage: ${payload[0].payload.usage} GB`}</p>
|
<p>{`Memory usage: ${payload[0].payload.usage} GB`}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ export const DockerNetworkChart = ({ acummulativeData }: Props) => {
|
|||||||
return {
|
return {
|
||||||
time: item.time,
|
time: item.time,
|
||||||
name: `Point ${index + 1}`,
|
name: `Point ${index + 1}`,
|
||||||
inMB: item.value.inputMb.toFixed(2),
|
inMB: item.value.inputMb,
|
||||||
outMB: item.value.outputMb.toFixed(2),
|
outMB: item.value.outputMb,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
@@ -86,9 +86,11 @@ const CustomTooltip = ({ active, payload }: CustomTooltipProps) => {
|
|||||||
if (active && payload && payload.length && payload[0]) {
|
if (active && payload && payload.length && payload[0]) {
|
||||||
return (
|
return (
|
||||||
<div className="custom-tooltip bg-background p-2 shadow-lg rounded-md text-primary border">
|
<div className="custom-tooltip bg-background p-2 shadow-lg rounded-md text-primary border">
|
||||||
<p>{`Date: ${format(new Date(payload[0].payload.time), "PPpp")}`}</p>
|
{payload[0].payload.time && (
|
||||||
<p>{`In MB Usage: ${payload[0].payload.inMB} MB`}</p>
|
<p>{`Date: ${format(new Date(payload[0].payload.time), "PPpp")}`}</p>
|
||||||
<p>{`Out MB Usage: ${payload[0].payload.outMB} MB`}</p>
|
)}
|
||||||
|
<p>{`In Usage: ${payload[0].payload.inMB} `}</p>
|
||||||
|
<p>{`Out Usage: ${payload[0].payload.outMB} `}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ const defaultData = {
|
|||||||
memory: {
|
memory: {
|
||||||
value: {
|
value: {
|
||||||
used: 0,
|
used: 0,
|
||||||
free: 0,
|
|
||||||
usedPercentage: 0,
|
|
||||||
total: 0,
|
total: 0,
|
||||||
},
|
},
|
||||||
time: "",
|
time: "",
|
||||||
@@ -60,8 +58,6 @@ export interface DockerStats {
|
|||||||
memory: {
|
memory: {
|
||||||
value: {
|
value: {
|
||||||
used: number;
|
used: number;
|
||||||
free: number;
|
|
||||||
usedPercentage: number;
|
|
||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
time: string;
|
time: string;
|
||||||
@@ -100,6 +96,30 @@ export type DockerStatsJSON = {
|
|||||||
disk: DockerStats["disk"][];
|
disk: DockerStats["disk"][];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const convertMemoryToBytes = (
|
||||||
|
memoryString: string | undefined,
|
||||||
|
): number => {
|
||||||
|
if (!memoryString || typeof memoryString !== "string") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = Number.parseFloat(memoryString) || 0;
|
||||||
|
const unit = memoryString.replace(/[0-9.]/g, "").trim();
|
||||||
|
|
||||||
|
switch (unit) {
|
||||||
|
case "KiB":
|
||||||
|
return value * 1024;
|
||||||
|
case "MiB":
|
||||||
|
return value * 1024 * 1024;
|
||||||
|
case "GiB":
|
||||||
|
return value * 1024 * 1024 * 1024;
|
||||||
|
case "TiB":
|
||||||
|
return value * 1024 * 1024 * 1024 * 1024;
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const DockerMonitoring = ({
|
export const DockerMonitoring = ({
|
||||||
appName,
|
appName,
|
||||||
appType = "application",
|
appType = "application",
|
||||||
@@ -208,7 +228,7 @@ export const DockerMonitoring = ({
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
Used: {currentData.cpu.value.toFixed(2)}%
|
Used: {currentData.cpu.value}%
|
||||||
</span>
|
</span>
|
||||||
<Progress
|
<Progress
|
||||||
value={currentData.cpu.value}
|
value={currentData.cpu.value}
|
||||||
@@ -218,7 +238,6 @@ export const DockerMonitoring = ({
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="bg-background">
|
<Card className="bg-background">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium">
|
<CardTitle className="text-sm font-medium">
|
||||||
@@ -228,20 +247,26 @@ export const DockerMonitoring = ({
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{`Used: ${(currentData.memory.value.used / 1024 ** 3).toFixed(2)} GB / Limit: ${(currentData.memory.value.total / 1024 ** 3).toFixed(2)} GB`}
|
{`Used: ${currentData.memory.value.used} / Limit: ${currentData.memory.value.total} `}
|
||||||
</span>
|
</span>
|
||||||
<Progress
|
<Progress
|
||||||
value={currentData.memory.value.usedPercentage}
|
value={
|
||||||
|
(convertMemoryToBytes(currentData.memory.value.used) /
|
||||||
|
convertMemoryToBytes(currentData.memory.value.total)) *
|
||||||
|
100
|
||||||
|
}
|
||||||
className="w-[100%]"
|
className="w-[100%]"
|
||||||
/>
|
/>
|
||||||
<DockerMemoryChart
|
<DockerMemoryChart
|
||||||
acummulativeData={acummulativeData.memory}
|
acummulativeData={acummulativeData.memory}
|
||||||
memoryLimitGB={currentData.memory.value.total / 1024 ** 3}
|
memoryLimitGB={
|
||||||
|
convertMemoryToBytes(currentData.memory.value.total) /
|
||||||
|
1024 ** 3
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{appName === "dokploy" && (
|
{appName === "dokploy" && (
|
||||||
<Card className="bg-background">
|
<Card className="bg-background">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
@@ -274,17 +299,12 @@ export const DockerMonitoring = ({
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{`Read: ${currentData.block.value.readMb.toFixed(
|
{`Read: ${currentData.block.value.readMb} / Write: ${currentData.block.value.writeMb} `}
|
||||||
2,
|
|
||||||
)} MB / Write: ${currentData.block.value.writeMb.toFixed(
|
|
||||||
3,
|
|
||||||
)} MB`}
|
|
||||||
</span>
|
</span>
|
||||||
<DockerBlockChart acummulativeData={acummulativeData.block} />
|
<DockerBlockChart acummulativeData={acummulativeData.block} />
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="bg-background">
|
<Card className="bg-background">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium">
|
<CardTitle className="text-sm font-medium">
|
||||||
@@ -294,11 +314,7 @@ export const DockerMonitoring = ({
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{`In MB: ${currentData.network.value.inputMb.toFixed(
|
{`In MB: ${currentData.network.value.inputMb} / Out MB: ${currentData.network.value.outputMb} `}
|
||||||
2,
|
|
||||||
)} MB / Out MB: ${currentData.network.value.outputMb.toFixed(
|
|
||||||
2,
|
|
||||||
)} MB`}
|
|
||||||
</span>
|
</span>
|
||||||
<DockerNetworkChart
|
<DockerNetworkChart
|
||||||
acummulativeData={acummulativeData.network}
|
acummulativeData={acummulativeData.network}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type http from "node:http";
|
import type http from "node:http";
|
||||||
import {
|
import {
|
||||||
docker,
|
docker,
|
||||||
|
execAsync,
|
||||||
getLastAdvancedStatsFile,
|
getLastAdvancedStatsFile,
|
||||||
recordAdvancedStats,
|
recordAdvancedStats,
|
||||||
validateWebSocketRequest,
|
validateWebSocketRequest,
|
||||||
@@ -70,12 +71,16 @@ export const setupDockerStatsMonitoringSocketServer = (
|
|||||||
ws.close(4000, "Container not running");
|
ws.close(4000, "Container not running");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const { stdout, stderr } = await execAsync(
|
||||||
|
`docker stats ${container.Id} --no-stream --format \'{"BlockIO":"{{.BlockIO}}","CPUPerc":"{{.CPUPerc}}","Container":"{{.Container}}","ID":"{{.ID}}","MemPerc":"{{.MemPerc}}","MemUsage":"{{.MemUsage}}","Name":"{{.Name}}","NetIO":"{{.NetIO}}"}\'`,
|
||||||
|
);
|
||||||
|
if (stderr) {
|
||||||
|
console.error("Docker stats error:", stderr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const stat = JSON.parse(stdout);
|
||||||
|
|
||||||
const stats = await docker.getContainer(container.Id).stats({
|
await recordAdvancedStats(stat, appName);
|
||||||
stream: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
await recordAdvancedStats(stats, appName);
|
|
||||||
const data = await getLastAdvancedStatsFile(appName);
|
const data = await getLastAdvancedStatsFile(appName);
|
||||||
|
|
||||||
ws.send(
|
ws.send(
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export * from "./utils/access-log/types";
|
|||||||
export * from "./utils/access-log/utils";
|
export * from "./utils/access-log/utils";
|
||||||
export * from "./constants/index";
|
export * from "./constants/index";
|
||||||
|
|
||||||
export * from "./monitoring/utilts";
|
export * from "./monitoring/utils";
|
||||||
|
|
||||||
export * from "./db/validations/domain";
|
export * from "./db/validations/domain";
|
||||||
export * from "./db/validations/index";
|
export * from "./db/validations/index";
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
import { promises } from "node:fs";
|
import { promises } from "node:fs";
|
||||||
import type Dockerode from "dockerode";
|
|
||||||
import osUtils from "node-os-utils";
|
import osUtils from "node-os-utils";
|
||||||
import { paths } from "../constants";
|
import { paths } from "../constants";
|
||||||
|
|
||||||
|
export interface Container {
|
||||||
|
BlockIO: string;
|
||||||
|
CPUPerc: string;
|
||||||
|
Container: string;
|
||||||
|
ID: string;
|
||||||
|
MemPerc: string;
|
||||||
|
MemUsage: string;
|
||||||
|
Name: string;
|
||||||
|
NetIO: string;
|
||||||
|
}
|
||||||
export const recordAdvancedStats = async (
|
export const recordAdvancedStats = async (
|
||||||
stats: Dockerode.ContainerStats,
|
stats: Container,
|
||||||
appName: string,
|
appName: string,
|
||||||
) => {
|
) => {
|
||||||
const { MONITORING_PATH } = paths();
|
const { MONITORING_PATH } = paths();
|
||||||
@@ -12,29 +21,20 @@ export const recordAdvancedStats = async (
|
|||||||
|
|
||||||
await promises.mkdir(path, { recursive: true });
|
await promises.mkdir(path, { recursive: true });
|
||||||
|
|
||||||
const cpuPercent = calculateCpuUsagePercent(
|
await updateStatsFile(appName, "cpu", stats.CPUPerc);
|
||||||
stats.cpu_stats,
|
|
||||||
stats.precpu_stats,
|
|
||||||
);
|
|
||||||
const memoryStats = calculateMemoryStats(stats.memory_stats);
|
|
||||||
const blockIO = calculateBlockIO(stats.blkio_stats);
|
|
||||||
const networkUsage = calculateNetworkUsage(stats.networks);
|
|
||||||
|
|
||||||
await updateStatsFile(appName, "cpu", cpuPercent);
|
|
||||||
await updateStatsFile(appName, "memory", {
|
await updateStatsFile(appName, "memory", {
|
||||||
used: memoryStats.used,
|
used: stats.MemUsage.split(" ")[0],
|
||||||
free: memoryStats.free,
|
total: stats.MemUsage.split(" ")[2],
|
||||||
usedPercentage: memoryStats.usedPercentage,
|
|
||||||
total: memoryStats.total,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateStatsFile(appName, "block", {
|
await updateStatsFile(appName, "block", {
|
||||||
readMb: blockIO.readMb,
|
readMb: stats.BlockIO.split(" ")[0],
|
||||||
writeMb: blockIO.writeMb,
|
writeMb: stats.BlockIO.split(" ")[2],
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateStatsFile(appName, "network", {
|
await updateStatsFile(appName, "network", {
|
||||||
inputMb: networkUsage.inputMb,
|
inputMb: stats.NetIO.split(" ")[0],
|
||||||
outputMb: networkUsage.outputMb,
|
outputMb: stats.NetIO.split(" ")[2],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (appName === "dokploy") {
|
if (appName === "dokploy") {
|
||||||
@@ -122,77 +122,3 @@ export const getLastAdvancedStatsFile = async (appName: string) => {
|
|||||||
block: await readLastValueStatsFile(appName, "block"),
|
block: await readLastValueStatsFile(appName, "block"),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateCpuUsagePercent = (
|
|
||||||
cpu_stats: Dockerode.ContainerStats["cpu_stats"],
|
|
||||||
precpu_stats: Dockerode.ContainerStats["precpu_stats"],
|
|
||||||
) => {
|
|
||||||
const cpuDelta =
|
|
||||||
cpu_stats.cpu_usage.total_usage - precpu_stats.cpu_usage.total_usage;
|
|
||||||
const systemDelta =
|
|
||||||
cpu_stats.system_cpu_usage - precpu_stats.system_cpu_usage;
|
|
||||||
|
|
||||||
const numberCpus =
|
|
||||||
cpu_stats.online_cpus ||
|
|
||||||
(cpu_stats.cpu_usage.percpu_usage
|
|
||||||
? cpu_stats.cpu_usage.percpu_usage.length
|
|
||||||
: 1);
|
|
||||||
|
|
||||||
if (systemDelta > 0 && cpuDelta > 0) {
|
|
||||||
return (cpuDelta / systemDelta) * numberCpus * 100.0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateMemoryStats = (
|
|
||||||
memory_stats: Dockerode.ContainerStats["memory_stats"],
|
|
||||||
) => {
|
|
||||||
const usedMemory = memory_stats.usage - (memory_stats.stats.cache || 0);
|
|
||||||
const availableMemory = memory_stats.limit;
|
|
||||||
const memoryUsedPercentage = (usedMemory / availableMemory) * 100.0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
used: usedMemory,
|
|
||||||
free: availableMemory - usedMemory,
|
|
||||||
usedPercentage: memoryUsedPercentage,
|
|
||||||
total: availableMemory,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const calculateBlockIO = (
|
|
||||||
blkio_stats: Dockerode.ContainerStats["blkio_stats"],
|
|
||||||
) => {
|
|
||||||
let readIO = 0;
|
|
||||||
let writeIO = 0;
|
|
||||||
if (blkio_stats?.io_service_bytes_recursive) {
|
|
||||||
for (const io of blkio_stats.io_service_bytes_recursive) {
|
|
||||||
if (io.op === "read") {
|
|
||||||
readIO += io.value;
|
|
||||||
} else if (io.op === "write") {
|
|
||||||
writeIO += io.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
readMb: readIO / (1024 * 1024),
|
|
||||||
writeMb: writeIO / (1024 * 1024),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateNetworkUsage = (
|
|
||||||
networks: Dockerode.ContainerStats["networks"],
|
|
||||||
) => {
|
|
||||||
let totalRx = 0;
|
|
||||||
let totalTx = 0;
|
|
||||||
|
|
||||||
const stats = Object.keys(networks);
|
|
||||||
|
|
||||||
for (const interfaceName of stats) {
|
|
||||||
const net = networks[interfaceName];
|
|
||||||
totalRx += net?.rx_bytes || 0;
|
|
||||||
totalTx += net?.tx_bytes || 0;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
inputMb: totalRx / (1024 * 1024),
|
|
||||||
outputMb: totalTx / (1024 * 1024),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
buildAppName,
|
buildAppName,
|
||||||
cleanAppName,
|
cleanAppName,
|
||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
import { getAdvancedStats } from "@dokploy/server/monitoring/utilts";
|
import { getAdvancedStats } from "@dokploy/server/monitoring/utils";
|
||||||
import {
|
import {
|
||||||
buildApplication,
|
buildApplication,
|
||||||
getBuildCommand,
|
getBuildCommand,
|
||||||
|
|||||||
Reference in New Issue
Block a user