Updated chart for mobile
This commit is contained in:
@@ -2,12 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
|
|||||||
import testAuth from "../auth";
|
import testAuth from "../auth";
|
||||||
import { StatusCodes } from "http-status-codes";
|
import { StatusCodes } from "http-status-codes";
|
||||||
import si from "systeminformation";
|
import si from "systeminformation";
|
||||||
|
import { average } from "@/lib/format";
|
||||||
function average<T>(values: Array<T>, p: (v: T) => number) {
|
|
||||||
return values.length > 0
|
|
||||||
? values.reduce((sum, value) => sum + p(value), 0) / values.length
|
|
||||||
: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Usage = {
|
type Usage = {
|
||||||
cpu: number;
|
cpu: number;
|
||||||
|
|||||||
+78
-40
@@ -7,6 +7,11 @@ import { type ChartConfig } from "@/components/ui/chart";
|
|||||||
import GradientAreaChart from "@/components/GradientAreaChart";
|
import GradientAreaChart from "@/components/GradientAreaChart";
|
||||||
import { getMetrics } from "@/core/client/metrics";
|
import { getMetrics } from "@/core/client/metrics";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useIsMobile } from "@/hooks/use-mobile";
|
||||||
|
import RadialChart from "@/components/RadialChart";
|
||||||
|
import { Node, NodeLiveData } from "@/lib/types";
|
||||||
|
import { getLiveNodes } from "@/core/client/node";
|
||||||
|
import { average } from "@/lib/format";
|
||||||
|
|
||||||
const containerMetrics = [
|
const containerMetrics = [
|
||||||
{ name: "api-gateway-1", cpu: 45, memory: 62, network: "12.4 MB/s" },
|
{ name: "api-gateway-1", cpu: 45, memory: 62, network: "12.4 MB/s" },
|
||||||
@@ -42,6 +47,24 @@ export default function MonitoringPage() {
|
|||||||
out: number;
|
out: number;
|
||||||
}[]
|
}[]
|
||||||
>([]);
|
>([]);
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const [liveData, setLiveData] = useState<{
|
||||||
|
cpuUsage: number;
|
||||||
|
ramUsage: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchNodes = async () => {
|
||||||
|
const nodes = await getLiveNodes();
|
||||||
|
setLiveData({
|
||||||
|
cpuUsage: average(nodes, (n) => n.liveData.cpu.usage),
|
||||||
|
ramUsage: average(nodes, (n) => n.liveData.memory.usage),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
fetchNodes();
|
||||||
|
const interval = setInterval(fetchNodes, 3000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchMetrics() {
|
async function fetchMetrics() {
|
||||||
@@ -61,46 +84,61 @@ export default function MonitoringPage() {
|
|||||||
subtitle="Resource utilization and metrics"
|
subtitle="Resource utilization and metrics"
|
||||||
>
|
>
|
||||||
{/* Summary Stats */}
|
{/* Summary Stats */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
{isMobile ? (
|
||||||
<Card>
|
<div className="grid grid-cols-2 gap-4 mb-6">
|
||||||
<CardContent className="p-4">
|
<Card>
|
||||||
<div className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
<CardContent className="px-4">
|
||||||
Cluster CPU
|
<RadialChart value={liveData?.cpuUsage || 0}>CPU</RadialChart>
|
||||||
</div>
|
</CardContent>
|
||||||
<div className="stat-value">68%</div>
|
</Card>
|
||||||
<Progress value={68} className="w-full" />
|
<Card>
|
||||||
</CardContent>
|
<CardContent className="px-4">
|
||||||
</Card>
|
<RadialChart value={liveData?.ramUsage || 0}>RAM</RadialChart>
|
||||||
<Card>
|
</CardContent>
|
||||||
<CardContent className="p-4">
|
</Card>
|
||||||
<div className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
</div>
|
||||||
Cluster Memory
|
) : (
|
||||||
</div>
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||||
<div className="stat-value">71%</div>
|
<Card>
|
||||||
<Progress value={71} className="w-full" />
|
<CardContent className="p-4">
|
||||||
</CardContent>
|
<div className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
||||||
</Card>
|
Cluster CPU
|
||||||
<Card>
|
</div>
|
||||||
<CardContent className="p-4">
|
<div className="stat-value">68%</div>
|
||||||
<div className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
<Progress value={68} className="w-full" />
|
||||||
Network In
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
<div className="stat-value">38 MB/s</div>
|
<Card>
|
||||||
<div className="text-xs text-success mt-1">↑ 12% from avg</div>
|
<CardContent className="p-4">
|
||||||
</CardContent>
|
<div className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
||||||
</Card>
|
Cluster Memory
|
||||||
<Card>
|
</div>
|
||||||
<CardContent className="p-4">
|
<div className="stat-value">71%</div>
|
||||||
<div className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
<Progress value={71} className="w-full" />
|
||||||
Network Out
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
<div className="stat-value">28 MB/s</div>
|
<Card>
|
||||||
<div className="text-xs text-muted-foreground mt-1">
|
<CardContent className="p-4">
|
||||||
↓ 5% from avg
|
<div className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
||||||
</div>
|
Network In
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
<div className="stat-value">38 MB/s</div>
|
||||||
</div>
|
<div className="text-xs text-success mt-1">↑ 12% from avg</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
||||||
|
Network Out
|
||||||
|
</div>
|
||||||
|
<div className="stat-value">28 MB/s</div>
|
||||||
|
<div className="text-xs text-muted-foreground mt-1">
|
||||||
|
↓ 5% from avg
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* CPU & Memory Charts */}
|
{/* CPU & Memory Charts */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Label,
|
Label,
|
||||||
PolarGrid,
|
PolarAngleAxis,
|
||||||
PolarRadiusAxis,
|
PolarRadiusAxis,
|
||||||
RadialBar,
|
RadialBar,
|
||||||
RadialBarChart,
|
RadialBarChart,
|
||||||
@@ -15,6 +15,8 @@ const chartConfig = {
|
|||||||
},
|
},
|
||||||
} satisfies ChartConfig;
|
} satisfies ChartConfig;
|
||||||
|
|
||||||
|
const INNER_RADIUS = 80;
|
||||||
|
|
||||||
export default function RadialChart({
|
export default function RadialChart({
|
||||||
value,
|
value,
|
||||||
children,
|
children,
|
||||||
@@ -23,22 +25,25 @@ export default function RadialChart({
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const chartData = [{ value, fill: "var(--color-chart-1)" }];
|
const chartData = [{ value, fill: "var(--color-chart-1)" }];
|
||||||
|
const startAngle = 230;
|
||||||
|
const totalSweep = 280;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChartContainer config={chartConfig} className="size-30">
|
<ChartContainer config={chartConfig} className="w-full aspect-square">
|
||||||
<RadialBarChart
|
<RadialBarChart
|
||||||
data={chartData}
|
data={chartData}
|
||||||
startAngle={-50}
|
startAngle={startAngle}
|
||||||
endAngle={(value / 100) * 230}
|
endAngle={startAngle - (value / 100) * totalSweep}
|
||||||
innerRadius="65%"
|
innerRadius={INNER_RADIUS}
|
||||||
outerRadius="100%"
|
outerRadius={INNER_RADIUS + 30}
|
||||||
>
|
>
|
||||||
<PolarGrid
|
<PolarAngleAxis
|
||||||
gridType="circle"
|
type="number"
|
||||||
radialLines={false}
|
domain={[0, 100]}
|
||||||
stroke="none"
|
tick={false}
|
||||||
className=""
|
tickLine={false}
|
||||||
polarRadius={[86, 74]}
|
axisLine={false}
|
||||||
|
ticks={[]}
|
||||||
/>
|
/>
|
||||||
<RadialBar dataKey="value" background cornerRadius={10} />
|
<RadialBar dataKey="value" background cornerRadius={10} />
|
||||||
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
|
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
|
||||||
@@ -57,7 +62,7 @@ export default function RadialChart({
|
|||||||
y={viewBox.cy}
|
y={viewBox.cy}
|
||||||
className="fill-foreground text-xl font-bold"
|
className="fill-foreground text-xl font-bold"
|
||||||
>
|
>
|
||||||
{chartData[0].value.toLocaleString()}
|
{chartData[0].value.toFixed(1)}%
|
||||||
</tspan>
|
</tspan>
|
||||||
<tspan
|
<tspan
|
||||||
x={viewBox.cx}
|
x={viewBox.cx}
|
||||||
|
|||||||
@@ -9,3 +9,9 @@ export function formatBytes(bytes: number, decimals = 2) {
|
|||||||
sizes[i]
|
sizes[i]
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function average<T>(values: Array<T>, p: (v: T) => number) {
|
||||||
|
return values.length > 0
|
||||||
|
? values.reduce((sum, value) => sum + p(value), 0) / values.length
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user