feat/enhance app volume monitoring with usage percentages and total capacity display

This commit is contained in:
biersoeckli
2025-01-09 10:01:33 +00:00
parent 6abe60c20f
commit 487a635472
5 changed files with 55 additions and 19 deletions

View File

@@ -22,6 +22,9 @@ import { Progress } from "@/components/ui/progress"
import dataAccess from '@/server/adapter/db.client';
import { ProgressIndicator } from '@radix-ui/react-progress';
type AppVolumeMonitoringUsageExtendedModel = AppVolumeMonitoringUsageModel & {
usedPercentage: number;
};
export default function AppVolumeMonitoring({
volumesUsage
@@ -29,26 +32,44 @@ export default function AppVolumeMonitoring({
volumesUsage?: AppVolumeMonitoringUsageModel[]
}) {
const [updatedVolumeUsage, setUpdatedVolumeUsage] = useState<(AppVolumeMonitoringUsageModel & { usedPercentage: number; })[] | undefined>(volumesUsage?.map(item => ({
...item,
usedPercentage: Math.round(item.usedBytes / item.capacityBytes * 100)
})));
const convertToExtendedModel = (input?: AppVolumeMonitoringUsageModel[]): AppVolumeMonitoringUsageExtendedModel[] | undefined => {
if (input) {
return input.map(item => ({
...item,
usedPercentage: Math.round(item.usedBytes / item.capacityBytes * 100)
}));
}
return undefined;
}
const [totalUsedBytes, setTotalUsedBytes] = useState<number | undefined>(undefined);
const [totalCapacityBytes, setTotalCapacityBytes] = useState<number | undefined>(undefined);
const [updatedVolumeUsage, setUpdatedVolumeUsage] = useState<AppVolumeMonitoringUsageExtendedModel[] | undefined>(convertToExtendedModel(volumesUsage));
const fetchVolumeMonitoringUsage = async () => {
try {
const data = await Actions.run(() => getVolumeMonitoringUsage());
setUpdatedVolumeUsage(data.map(item => ({
...item,
usedPercentage: Math.round(item.usedBytes / item.capacityBytes * 100)
})));
setUpdatedVolumeUsage(convertToExtendedModel(data));
setUsedAndCapacityBytes(convertToExtendedModel(data));
} catch (ex) {
toast.error('An error occurred while fetching current volume usage');
console.error('An error occurred while fetching volume nodes', ex);
}
}
const setUsedAndCapacityBytes = (input?: AppVolumeMonitoringUsageExtendedModel[]) => {
if (input) {
const totalUsed = input.reduce((acc, item) => acc + item.usedBytes, 0);
const totalCapacity = input.reduce((acc, item) => acc + item.capacityBytes, 0);
setTotalUsedBytes(totalUsed);
setTotalCapacityBytes(totalCapacity);
}
}
useEffect(() => {
const volumeUsageId = setInterval(() => fetchVolumeMonitoringUsage(), 10000);
setUsedAndCapacityBytes(convertToExtendedModel(volumesUsage));
return () => {
clearInterval(volumeUsageId);
}
@@ -69,7 +90,10 @@ export default function AppVolumeMonitoring({
</CardHeader>
<CardContent>
<Table>
<TableCaption>{updatedVolumeUsage.length} App Volumes</TableCaption>
<TableCaption>{updatedVolumeUsage.length} App Volumes {totalUsedBytes && totalCapacityBytes && <>
<span className='text-slate-500'> | Total used {KubeSizeConverter.convertBytesToReadableSize(totalUsedBytes)} | Total allocated {KubeSizeConverter.convertBytesToReadableSize(totalCapacityBytes)}</span>
</>}
</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Project</TableHead>

View File

@@ -66,7 +66,7 @@ export const getPvcUsage = async (appId: string, projectId: string) =>
simpleAction(async () => {
await getAuthUserSession();
return monitoringService.getPvcUsageFromApp(appId, projectId);
}) as Promise<ServerActionResult<any, { pvcName: string, usage: number }[]>>;
}) as Promise<ServerActionResult<any, { pvcName: string, usedBytes: number }[]>>;
export const downloadPvcData = async (volumeId: string) =>
simpleAction(async () => {

View File

@@ -20,8 +20,10 @@ import {
} from "@/components/ui/tooltip"
import { Code } from "@/components/custom/code";
import { Label } from "@/components/ui/label";
import { KubeSizeConverter } from "@/shared/utils/kubernetes-size-converter.utils";
import { Progress } from "@/components/ui/progress";
type AppVolumeWithCapacity = (AppVolume & { capacity?: string });
type AppVolumeWithCapacity = (AppVolume & { usedBytes?: number; capacityBytes?: number; usedPercentage?: number });
export default function StorageList({ app }: {
app: AppExtendedModel
@@ -39,7 +41,9 @@ export default function StorageList({ app }: {
for (let item of mappedVolumeData) {
const volume = response.data.find(x => x.pvcName === KubeObjectNameUtils.toPvcName(item.id));
if (volume) {
item.capacity = `${volume.usage.toFixed(2)} MB (${(volume.usage / item.size * 100).toFixed(2)}%)`;
item.usedBytes = volume.usedBytes;
item.capacityBytes = KubeSizeConverter.fromMegabytesToBytes(item.size);
item.usedPercentage = Math.round(volume.usedBytes / item.capacityBytes * 100);
}
}
setVolumesWithStorage(mappedVolumeData);
@@ -154,7 +158,15 @@ export default function StorageList({ app }: {
<TableRow key={volume.containerMountPath}>
<TableCell className="font-medium">{volume.containerMountPath}</TableCell>
<TableCell className="font-medium">{volume.size} MB</TableCell>
<TableCell className="font-medium">{volume.capacity}</TableCell>
<TableCell className="font-medium space-y-2">
{volume.usedPercentage && <>
<Progress value={volume.usedPercentage}
color={volume.usedPercentage >= 90 ? 'red' : (volume.usedPercentage >= 80 ? 'orange' : undefined)} />
<div className='text-xs text-slate-500'>
{KubeSizeConverter.convertBytesToReadableSize(volume.usedBytes!)} used ({volume.usedPercentage}%)
</div>
</>}
</TableCell>
<TableCell className="font-medium">{volume.accessMode}</TableCell>
<TableCell className="font-medium flex gap-2">
<TooltipProvider>

View File

@@ -21,9 +21,9 @@ class LonghornApiAdapter {
const data = await response.json();
const usedStorage = data.controllers?.[0]?.actualSize;
const usedStorageBytes = data.controllers?.[0]?.actualSize;
return (usedStorage / (1024 * 1024));
return usedStorageBytes;
}
async getAllLonghornVolumes(): Promise<{

View File

@@ -108,9 +108,9 @@ class MonitorService {
}
}
async getPvcUsageFromApp(appId: string, projectId: string): Promise<Array<{ pvcName: string, usage: number }>> {
async getPvcUsageFromApp(appId: string, projectId: string): Promise<Array<{ pvcName: string, usedBytes: number }>> {
const pvcFromApp = await pvcService.getAllPvcForApp(projectId, appId);
const pvcUsageData: Array<{ pvcName: string, usage: number }> = [];
const pvcUsageData: Array<{ pvcName: string, usedBytes: number }> = [];
for (const pvc of pvcFromApp) {
const pvcName = pvc.metadata?.name;
@@ -118,8 +118,8 @@ class MonitorService {
if (pvcName && volumeName) {
const usage = await longhornApiAdapter.getLonghornVolume(volumeName);
pvcUsageData.push({ pvcName, usage });
const usedBytes = await longhornApiAdapter.getLonghornVolume(volumeName);
pvcUsageData.push({ pvcName, usedBytes });
}
}
return pvcUsageData;