mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-02-11 05:59:23 -06:00
feat/enhance app volume monitoring with usage percentages and total capacity display
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user