refactor: make request to dokploy server to proxy requests

This commit is contained in:
Mauricio Siu 2025-02-02 19:32:37 -06:00
parent 74a0f5e992
commit 0c8c0844b1
10 changed files with 209 additions and 165 deletions

View File

@ -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<ContainerMetric>(
{} as ContainerMetric,
);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [dataPoints, setDataPoints] =
useState<keyof typeof DATA_POINTS_OPTIONS>("50");
const [refreshInterval, setRefreshInterval] = useState<string>("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 (
<div className="mt-5 flex min-h-[55vh] w-full items-center justify-center p-4">
<div className="max-w-xl text-center">
@ -166,7 +119,9 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {
<strong className="text-primary">{appName}</strong>
</p>
<p className="whitespace-pre-line text-sm text-destructive">
{error}
{queryError instanceof Error
? queryError.message
: "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}
</p>
<p className=" text-sm text-muted-foreground">URL: {baseUrl}</p>
</div>
@ -238,11 +193,11 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {
<h3 className="text-sm font-medium">Memory Usage</h3>
</div>
<p className="mt-2 text-2xl font-bold">
{metrics.Memory.percentage}%
{metrics?.Memory?.percentage}%
</p>
<p className="mt-1 text-sm text-muted-foreground">
{metrics.Memory.used} {metrics.Memory.unit} / {metrics.Memory.total}{" "}
{metrics.Memory.unit}
{metrics?.Memory?.used} {metrics?.Memory?.unit} /{" "}
{metrics?.Memory?.total} {metrics?.Memory?.unit}
</p>
</Card>
@ -252,8 +207,8 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {
<h3 className="text-sm font-medium">Network I/O</h3>
</div>
<p className="mt-2 text-2xl font-bold">
{metrics.Network.input} {metrics.Network.inputUnit} /{" "}
{metrics.Network.output} {metrics.Network.outputUnit}
{metrics?.Network?.input} {metrics?.Network?.inputUnit} /{" "}
{metrics?.Network?.output} {metrics?.Network?.outputUnit}
</p>
</Card>
@ -263,8 +218,8 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {
<h3 className="text-sm font-medium">Block I/O</h3>
</div>
<p className="mt-2 text-2xl font-bold">
{metrics.BlockIO.read} {metrics.BlockIO.readUnit} /{" "}
{metrics.BlockIO.write} {metrics.BlockIO.writeUnit}
{metrics?.BlockIO?.read} {metrics?.BlockIO?.readUnit} /{" "}
{metrics?.BlockIO?.write} {metrics?.BlockIO?.writeUnit}
</p>
</Card>
</div>

View File

@ -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<SystemMetrics[]>([]);
const [metrics, setMetrics] = useState<SystemMetrics>({} as SystemMetrics);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [dataPoints, setDataPoints] =
useState<keyof typeof DATA_POINTS_OPTIONS>("50");
const [refreshInterval, setRefreshInterval] = useState<string>("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 (
<div className="flex h-[400px] w-full items-center justify-center">
@ -165,7 +132,7 @@ export const ShowPaidMonitoring = ({
);
}
if (error) {
if (queryError) {
return (
<div className="flex min-h-[55vh] w-full items-center justify-center p-4">
<div className="max-w-xl text-center">
@ -173,7 +140,9 @@ export const ShowPaidMonitoring = ({
Error fetching metrics{" "}
</p>
<p className="whitespace-pre-line text-sm text-destructive">
{error}
{queryError instanceof Error
? queryError.message
: "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}
</p>
<p className=" text-sm text-muted-foreground">URL: {BASE_URL}</p>
</div>

View File

@ -399,6 +399,8 @@ export async function getServerSideProps(
applicationId: params?.applicationId,
});
await helpers.settings.isCloud.prefetch();
return {
props: {
trpcState: helpers.dehydrate(),

View File

@ -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(),

View File

@ -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(),

View File

@ -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(),

View File

@ -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(),

View File

@ -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(),

View File

@ -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(),

View File

@ -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;
}
}),
});