mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-02-11 05:59:23 -06:00
fix/fixed disk chart information
This commit is contained in:
234
src/app/monitoring/disk-chart.tsx
Normal file
234
src/app/monitoring/disk-chart.tsx
Normal file
@@ -0,0 +1,234 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Label,
|
||||
PolarGrid,
|
||||
PolarRadiusAxis,
|
||||
RadialBar,
|
||||
RadialBarChart,
|
||||
} from 'recharts';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
|
||||
import { NodeResourceModel } from '@/shared/model/node-resource.model';
|
||||
import { useEffect } from 'react';
|
||||
import { StringUtils } from '@/shared/utils/string.utils';
|
||||
|
||||
export default function ChartDiskRessources({
|
||||
nodeRessource,
|
||||
}: {
|
||||
nodeRessource: NodeResourceModel;
|
||||
}) {
|
||||
|
||||
const chartData = [{
|
||||
diskUsed: nodeRessource.diskUsageAbsolut, //* 360 / nodeRessource.diskUsageCapacity,
|
||||
diskReserved: nodeRessource.diskUsageReserved, //* 360 / nodeRessource.diskUsageCapacity,
|
||||
diskSchedulable: nodeRessource.diskSpaceSchedulable
|
||||
}];
|
||||
|
||||
const chartConfig = {
|
||||
diskUsed: {
|
||||
label: "Used",
|
||||
color: "hsl(var(--chart-1))",
|
||||
},
|
||||
diskReserved: {
|
||||
label: "Reserved (free but not usable)",
|
||||
color: "hsl(var(--chart-2))",
|
||||
},
|
||||
diskSchedulable: {
|
||||
label: "Schedulable",
|
||||
color: "hsl(var(--muted))",
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
return (<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Disk</CardTitle>
|
||||
<CardDescription>Usage in %</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="flex flex-1 items-center pb-0">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="mx-auto aspect-square w-full max-w-[250px]"
|
||||
>
|
||||
<RadialBarChart
|
||||
data={chartData}
|
||||
innerRadius={80}
|
||||
outerRadius={110}
|
||||
>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent hideLabel formatter={(value, name) => {
|
||||
// Convert the value from bytes to gigabytes
|
||||
const formattedValue = StringUtils.convertBytesToReadableSize(value as number);
|
||||
// Optionally, you can customize the label (name) here if needed
|
||||
return <div className='flex gap-2'>
|
||||
<div className='self-center rounded w-2 h-2' style={{ backgroundColor: (chartConfig as any)[name].color }}></div>
|
||||
<div className='flex-1'>{(chartConfig as any)[name].label}:</div>
|
||||
<div>{formattedValue}</div>
|
||||
</div>
|
||||
}} />}
|
||||
/>
|
||||
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (
|
||||
viewBox &&
|
||||
'cx' in viewBox &&
|
||||
'cy' in viewBox
|
||||
) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-4xl font-bold"
|
||||
>
|
||||
{((nodeRessource.diskUsageAbsolut + nodeRessource.diskUsageReserved) / nodeRessource.diskUsageCapacity * 100).toFixed(1)}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
%
|
||||
</tspan>
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
<RadialBar
|
||||
dataKey="diskUsed"
|
||||
stackId="a"
|
||||
cornerRadius={5}
|
||||
fill="var(--color-diskUsed)"
|
||||
className="stroke-transparent stroke-2"
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="diskReserved"
|
||||
fill="var(--color-diskReserved)"
|
||||
stackId="a"
|
||||
cornerRadius={5}
|
||||
className="stroke-transparent stroke-2"
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="diskSchedulable"
|
||||
fill="var(--color-diskSchedulable)"
|
||||
stackId="a"
|
||||
cornerRadius={5}
|
||||
className="stroke-transparent stroke-2"
|
||||
/>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
|
||||
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Disk Used + Reserved: {StringUtils.convertBytesToReadableSize(nodeRessource.diskUsageAbsolut + nodeRessource.diskUsageReserved)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Disk Schedulable: {StringUtils.convertBytesToReadableSize(nodeRessource.diskSpaceSchedulable)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Disk Capacity: {StringUtils.convertBytesToReadableSize(nodeRessource.diskUsageCapacity)}
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="mx-auto aspect-square max-h-[250px]">
|
||||
|
||||
<RadialBarChart
|
||||
data={chartData}
|
||||
innerRadius={80}
|
||||
outerRadius={110} >
|
||||
|
||||
<PolarGrid
|
||||
gridType="circle"
|
||||
radialLines={false}
|
||||
stroke="none"
|
||||
className="first:fill-muted last:fill-background"
|
||||
polarRadius={[86, 74]}
|
||||
/>
|
||||
|
||||
<RadialBar
|
||||
dataKey="diskUsed"
|
||||
stackId="a"
|
||||
background
|
||||
fill="var(--color-diskUsed)"
|
||||
cornerRadius={10}
|
||||
/>
|
||||
|
||||
<RadialBar
|
||||
dataKey="diskReserved"
|
||||
stackId="a"
|
||||
fill="var(--color-diskReserved)"
|
||||
background
|
||||
cornerRadius={10}
|
||||
/>
|
||||
|
||||
<PolarRadiusAxis
|
||||
tick={false}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (
|
||||
viewBox &&
|
||||
'cx' in viewBox &&
|
||||
'cy' in viewBox
|
||||
) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-4xl font-bold"
|
||||
>
|
||||
{(nodeRessource.diskUsageAbsolut / nodeRessource.diskUsageCapacity * 100).toFixed(1)}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
%
|
||||
</tspan>
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
*/
|
||||
@@ -1,4 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Label,
|
||||
PolarGrid,
|
||||
@@ -21,8 +22,9 @@ import {
|
||||
useBreadcrumbs,
|
||||
} from '@/frontend/states/zustand.states';
|
||||
import { useEffect } from 'react';
|
||||
import ChartDiskRessources from './disk-chart';
|
||||
|
||||
export default async function ResourcesNodes({
|
||||
export default function ResourcesNodes({
|
||||
resourcesNodes,
|
||||
}: {
|
||||
resourcesNodes: NodeResourceModel[];
|
||||
@@ -48,265 +50,187 @@ export default async function ResourcesNodes({
|
||||
);
|
||||
|
||||
return (
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{resourcesNodes.map((Node, index) => (
|
||||
<div key={index} className="space-y-4 rounded-lg border">
|
||||
<h3
|
||||
className={'p-4 rounded-t-lg font-semibold text-xl text-center'}
|
||||
>
|
||||
Node {index + 1}:<br/>
|
||||
{Node.name}
|
||||
</h3>
|
||||
<div className="space-y-2 px-4 pb-2">
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>CPU</CardTitle>
|
||||
<CardDescription>Usage in %</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="mx-auto aspect-square max-h-[250px]"
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 gap-8">
|
||||
{resourcesNodes.map((node, index) => (<>
|
||||
<h3
|
||||
className={'p-4 rounded-t-lg font-semibold text-xl text-center'}
|
||||
>
|
||||
Node {index + 1}:<br />
|
||||
{node.name}
|
||||
</h3>
|
||||
<div key={index} className="grid grid-cols-1 md:grid-cols-3">
|
||||
|
||||
<div className="space-y-2 px-4 pb-2">
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>CPU</CardTitle>
|
||||
<CardDescription>Usage in %</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="mx-auto aspect-square max-h-[250px]"
|
||||
>
|
||||
<RadialBarChart
|
||||
data={chartData}
|
||||
startAngle={0}
|
||||
endAngle={360 * node.cpuUsageAbsolut / node.cpuUsageCapacity}
|
||||
innerRadius={80}
|
||||
outerRadius={110}
|
||||
>
|
||||
<RadialBarChart
|
||||
data={chartData}
|
||||
startAngle={0}
|
||||
endAngle={360 * Node.cpuUsageAbsolut / Node.cpuUsageCapacity}
|
||||
innerRadius={80}
|
||||
outerRadius={110}
|
||||
<PolarGrid
|
||||
gridType="circle"
|
||||
radialLines={false}
|
||||
stroke="none"
|
||||
className="first:fill-muted last:fill-background"
|
||||
polarRadius={[86, 74]}
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="usage"
|
||||
background
|
||||
cornerRadius={10}
|
||||
/>
|
||||
<PolarRadiusAxis
|
||||
tick={false}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
>
|
||||
<PolarGrid
|
||||
gridType="circle"
|
||||
radialLines={false}
|
||||
stroke="none"
|
||||
className="first:fill-muted last:fill-background"
|
||||
polarRadius={[86, 74]}
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="usage"
|
||||
background
|
||||
cornerRadius={10}
|
||||
/>
|
||||
<PolarRadiusAxis
|
||||
tick={false}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (
|
||||
viewBox &&
|
||||
'cx' in viewBox &&
|
||||
'cy' in viewBox
|
||||
) {
|
||||
return (
|
||||
<text
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (
|
||||
viewBox &&
|
||||
'cx' in viewBox &&
|
||||
'cy' in viewBox
|
||||
) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
className="fill-foreground text-4xl font-bold"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-4xl font-bold"
|
||||
>
|
||||
{(Node.cpuUsageAbsolut / Node.cpuUsageCapacity * 100).toFixed(2)}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
%
|
||||
</tspan>
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
CPU Absolute: {Node.cpuUsageAbsolut.toFixed(2)} cores
|
||||
</div>
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
CPU Capacity: {Node.cpuUsageCapacity.toFixed(2)} cores
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="space-y-2 px-4 pb-2">
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>RAM</CardTitle>
|
||||
<CardDescription>Usage in %</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="mx-auto aspect-square max-h-[250px]"
|
||||
>
|
||||
<RadialBarChart
|
||||
data={chartData}
|
||||
startAngle={0}
|
||||
endAngle={360 * Node.ramUsageAbsolut / Node.ramUsageCapacity}
|
||||
innerRadius={80}
|
||||
outerRadius={110}
|
||||
>
|
||||
<PolarGrid
|
||||
gridType="circle"
|
||||
radialLines={false}
|
||||
stroke="none"
|
||||
className="first:fill-muted last:fill-background"
|
||||
polarRadius={[86, 74]}
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="usage"
|
||||
background
|
||||
cornerRadius={10}
|
||||
/>
|
||||
<PolarRadiusAxis
|
||||
tick={false}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (
|
||||
viewBox &&
|
||||
'cx' in viewBox &&
|
||||
'cy' in viewBox
|
||||
) {
|
||||
return (
|
||||
<text
|
||||
{(node.cpuUsageAbsolut / node.cpuUsageCapacity * 100).toFixed(1)}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-4xl font-bold"
|
||||
>
|
||||
{(Node.ramUsageAbsolut / Node.ramUsageCapacity * 100).toFixed(2)}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
%
|
||||
</tspan>
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
RAM Absolute: {(Node.ramUsageAbsolut / (1024 * 1024 * 1024)).toFixed(2)} GB
|
||||
</div>
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
RAM Capacity: {(Node.ramUsageCapacity / (1024 * 1024 * 1024)).toFixed(2)} GB
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="space-y-2 px-4 pb-2">
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Disk</CardTitle>
|
||||
<CardDescription>Usage in %</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="mx-auto aspect-square max-h-[250px]"
|
||||
>
|
||||
<RadialBarChart
|
||||
data={chartData}
|
||||
startAngle={0}
|
||||
endAngle={360 * Node.diskUsageAbsolut / Node.diskUsageCapacity}
|
||||
innerRadius={80}
|
||||
outerRadius={110}
|
||||
>
|
||||
<PolarGrid
|
||||
gridType="circle"
|
||||
radialLines={false}
|
||||
stroke="none"
|
||||
className="first:fill-muted last:fill-background"
|
||||
polarRadius={[86, 74]}
|
||||
%
|
||||
</tspan>
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="usage"
|
||||
background
|
||||
cornerRadius={10}
|
||||
/>
|
||||
<PolarRadiusAxis
|
||||
tick={false}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (
|
||||
viewBox &&
|
||||
'cx' in viewBox &&
|
||||
'cy' in viewBox
|
||||
) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-4xl font-bold"
|
||||
>
|
||||
{(Node.diskUsageAbsolut / Node.diskUsageCapacity * 100).toFixed(2)}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
%
|
||||
</tspan>
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Disk Absolute: {(Node.diskUsageAbsolut / (1024 * 1024 * 1024)).toFixed(2)} GB
|
||||
</div>
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Disk Capacity: {(Node.diskUsageCapacity / (1024 * 1024 * 1024)).toFixed(2)} GB
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</PolarRadiusAxis>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
CPU Absolute: {node.cpuUsageAbsolut.toFixed(2)} cores
|
||||
</div>
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
CPU Capacity: {node.cpuUsageCapacity.toFixed(2)} cores
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
<div className="space-y-2 px-4 pb-2">
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>RAM</CardTitle>
|
||||
<CardDescription>Usage in %</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="mx-auto aspect-square max-h-[250px]"
|
||||
>
|
||||
<RadialBarChart
|
||||
data={chartData}
|
||||
startAngle={0}
|
||||
endAngle={360 * node.ramUsageAbsolut / node.ramUsageCapacity}
|
||||
innerRadius={80}
|
||||
outerRadius={110}
|
||||
>
|
||||
<PolarGrid
|
||||
gridType="circle"
|
||||
radialLines={false}
|
||||
stroke="none"
|
||||
className="first:fill-muted last:fill-background"
|
||||
polarRadius={[86, 74]}
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="usage"
|
||||
background
|
||||
cornerRadius={10}
|
||||
/>
|
||||
<PolarRadiusAxis
|
||||
tick={false}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (
|
||||
viewBox &&
|
||||
'cx' in viewBox &&
|
||||
'cy' in viewBox
|
||||
) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-4xl font-bold"
|
||||
>
|
||||
{(node.ramUsageAbsolut / node.ramUsageCapacity * 100).toFixed(1)}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
%
|
||||
</tspan>
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
RAM Absolute: {(node.ramUsageAbsolut / (1024 * 1024 * 1024)).toFixed(2)} GB
|
||||
</div>
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
RAM Capacity: {(node.ramUsageCapacity / (1024 * 1024 * 1024)).toFixed(2)} GB
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="space-y-2 px-4 pb-2">
|
||||
<ChartDiskRessources nodeRessource={node} />
|
||||
</div>
|
||||
</div>
|
||||
</>))}
|
||||
</div>
|
||||
</CardContent>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import ResourceNodes from "./monitoring-nodes";
|
||||
|
||||
export default async function ResourceNodesInfoPage() {
|
||||
|
||||
await getAuthUserSession();
|
||||
const resourcesNode = await clusterService.getNodeResourceUsage();
|
||||
const session = await getAuthUserSession();
|
||||
return (
|
||||
<div className="flex-1 space-y-4 pt-6">
|
||||
<PageTitle
|
||||
title={'Resources Nodes'}
|
||||
subtitle={`View all resources of the nodes which belongs to the QuickStack Cluster.`}>
|
||||
subtitle={`View all resources of the nodes which belong to the QuickStack Cluster.`}>
|
||||
</PageTitle>
|
||||
<ResourceNodes resourcesNodes={resourcesNode} />
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
class LonghornApiAdapter {
|
||||
|
||||
async getLonghornVolume(pvcName: String) {
|
||||
let longhornApiUrl = process.env.NODE_ENV === 'production' ? 'http://longhorn-frontend.longhorn-system.svc.cluster.local/v1/volumes' : 'http://localhost:4000/v1/volumes';
|
||||
get longhornBaseUrl() {
|
||||
return process.env.NODE_ENV === 'production' ? 'http://longhorn-frontend.longhorn-system.svc.cluster.local' : 'http://localhost:4000';
|
||||
}
|
||||
|
||||
const response = await fetch(`${longhornApiUrl}/${pvcName}`, {
|
||||
async getLonghornVolume(pvcName: String) {
|
||||
const response = await fetch(`${this.longhornBaseUrl}/v1/volumes/${pvcName}`, {
|
||||
cache: 'no-cache',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@@ -26,9 +28,7 @@ class LonghornApiAdapter {
|
||||
|
||||
|
||||
async getNodeStorageInfo(nodeName: String) {
|
||||
let longhornApiUrl = process.env.NODE_ENV === 'production' ? 'http://longhorn-frontend.longhorn-system.svc.cluster.local/v1/nodes' : 'http://localhost:4000/v1/nodes';
|
||||
|
||||
const response = await fetch(`${longhornApiUrl}/${nodeName}`, {
|
||||
const response = await fetch(`${this.longhornBaseUrl}/v1/nodes/${nodeName}`, {
|
||||
cache: 'no-cache',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@@ -46,7 +46,9 @@ class LonghornApiAdapter {
|
||||
disks: {
|
||||
[key: string]: {
|
||||
storageMaximum: number,
|
||||
storageAvailable: number
|
||||
storageAvailable: number,
|
||||
storageReserved: number,
|
||||
storageScheduled: number
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -57,19 +59,93 @@ class LonghornApiAdapter {
|
||||
|
||||
let totalStorageMaximum = 0;
|
||||
let totalStorageAvailable = 0;
|
||||
|
||||
let totalStorageReserved = 0;
|
||||
let totalStorageScheduled = 0;
|
||||
Object.values(data.disks).forEach(disk => {
|
||||
|
||||
totalStorageMaximum += disk.storageMaximum;
|
||||
totalStorageAvailable += disk.storageAvailable;
|
||||
totalStorageReserved += disk.storageReserved;
|
||||
totalStorageScheduled += disk.storageScheduled;
|
||||
});
|
||||
|
||||
// The available Storage is the total storage minus the reserved storage (which is not available for scheduling --> 30% of disk space)
|
||||
const totalSchedulableStorage = totalStorageAvailable - totalStorageReserved;
|
||||
|
||||
return {
|
||||
totalStorageMaximum,
|
||||
totalStorageAvailable
|
||||
totalStorageAvailable,
|
||||
totalSchedulableStorage,
|
||||
totalStorageReserved
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
async backupPvc(pvcName: string) {
|
||||
const snapshot = await this.createSnapshot(pvcName);
|
||||
await this.createBackup(pvcName, snapshot.id);
|
||||
}
|
||||
|
||||
private async createBackup(pvcName: string, snapshotId: string) {
|
||||
const response = await fetch(`${this.longhornBaseUrl}/v1/volumes/${pvcName}?action=snapshotBackup`, {
|
||||
cache: 'no-cache',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"name": snapshotId
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP-Error: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
private async createSnapshot(pvcName: string) {
|
||||
const response = await fetch(`${this.longhornBaseUrl}/v1/volumes/${pvcName}?action=snapshotCreate`, {
|
||||
cache: 'no-cache',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP-Error: ${response.status}`);
|
||||
}
|
||||
|
||||
/*
|
||||
Response:
|
||||
{
|
||||
"actions": {},
|
||||
"checksum": "",
|
||||
"children": {
|
||||
"volume-head": true
|
||||
},
|
||||
"created": "2024-12-30T14:20:34Z",
|
||||
"id": "79973f60-02d5-4100-acd4-9306112d5c91",
|
||||
"labels": {},
|
||||
"links": {
|
||||
"self": "http://10.42.0.100:9500/v1/snapshots/79973f60-02d5-4100-acd4-9306112d5c91"
|
||||
},
|
||||
"name": "79973f60-02d5-4100-acd4-9306112d5c91",
|
||||
"parent": "4beb4c48-53cf-4eaa-98dc-cc3a91f71e44",
|
||||
"removed": false,
|
||||
"size": "0",
|
||||
"type": "snapshot",
|
||||
"usercreated": true
|
||||
}
|
||||
*/
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
}
|
||||
const longhornApiAdapter = new LonghornApiAdapter();
|
||||
export default longhornApiAdapter;
|
||||
47
src/server/services/network-policy.service.ts
Normal file
47
src/server/services/network-policy.service.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-same-namespace-and-external-traffic
|
||||
namespace: proj-databases-ce53614e
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector: {}
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: traefik
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: kube-system
|
||||
- from:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
egress:
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
- to:
|
||||
- namespaceSelector: {}
|
||||
podSelector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- to:
|
||||
- podSelector: {}
|
||||
|
||||
|
||||
*/
|
||||
@@ -65,7 +65,7 @@ class ClusterService {
|
||||
async getNodeResourceUsage(): Promise<NodeResourceModel[]> {
|
||||
const topNodes = await k8s.topNodes(k3s.core);
|
||||
|
||||
return await Promise.all (topNodes.map(async (node) => {
|
||||
return await Promise.all(topNodes.map(async (node) => {
|
||||
const diskInfo = await longhornApiAdapter.getNodeStorageInfo(node.Node.metadata?.name!);
|
||||
return {
|
||||
name: node.Node.metadata?.name!,
|
||||
@@ -74,9 +74,12 @@ class ClusterService {
|
||||
ramUsageAbsolut: Number(node.Memory?.RequestTotal!),
|
||||
ramUsageCapacity: Number(node.Memory?.Capacity!),
|
||||
diskUsageAbsolut: diskInfo.totalStorageMaximum - diskInfo.totalStorageAvailable,
|
||||
diskUsageReserved: diskInfo.totalStorageReserved,
|
||||
diskUsageCapacity: diskInfo.totalStorageMaximum,
|
||||
}}));
|
||||
}
|
||||
diskSpaceSchedulable: diskInfo.totalSchedulableStorage
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const clusterService = new ClusterService();
|
||||
|
||||
@@ -10,6 +10,8 @@ export const nodeResourceZodModel = z.object({
|
||||
ramUsageCapacity: z.number(),
|
||||
diskUsageAbsolut: z.number(),
|
||||
diskUsageCapacity: z.number(),
|
||||
diskUsageReserved: z.number(),
|
||||
diskSpaceSchedulable: z.number(),
|
||||
})
|
||||
|
||||
export type NodeResourceModel = z.infer<typeof nodeResourceZodModel>;
|
||||
Reference in New Issue
Block a user