mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-02-09 21:19:07 -06:00
fix: monitoring support for shared volumes and cache management for volumes in app detail
This commit is contained in:
@@ -47,7 +47,8 @@ export default function AppVolumeMonitoring({
|
||||
|
||||
const fetchVolumeMonitoringUsage = async () => {
|
||||
try {
|
||||
const data = await Actions.run(() => getVolumeMonitoringUsage());
|
||||
let data = await Actions.run(() => getVolumeMonitoringUsage());
|
||||
data = data?.filter((volume) => !!volume.isBaseVolume);
|
||||
setUpdatedVolumeUsage(convertToExtendedModel(data));
|
||||
setUsedAndCapacityBytes(convertToExtendedModel(data));
|
||||
} catch (ex) {
|
||||
|
||||
@@ -30,6 +30,8 @@ export default async function ResourceNodesInfoPage() {
|
||||
|
||||
// filter by role
|
||||
volumesUsage = volumesUsage?.filter((volume) => UserGroupUtils.sessionHasReadAccessForApp(session, volume.appId));
|
||||
// only base volumes, no shared volumes
|
||||
volumesUsage = volumesUsage?.filter((volume) => !!volume.isBaseVolume);
|
||||
updatedNodeRessources = updatedNodeRessources?.filter((app) => UserGroupUtils.sessionHasReadAccessForApp(session, app.appId));
|
||||
|
||||
return (
|
||||
|
||||
@@ -67,9 +67,13 @@ export default function SharedStorageEditDialog({ children, app }: {
|
||||
setIsLoadingVolumes(true);
|
||||
getShareableVolumes(app.id).then(result => {
|
||||
if (result.status === 'success' && result.data) {
|
||||
setShareableVolumes(result.data);
|
||||
const alreadyAddedSharedVolumes = app.appVolumes
|
||||
.filter(v => !!v.sharedVolumeId)
|
||||
.map(v => v.sharedVolumeId);
|
||||
setShareableVolumes(result.data.filter(v => !alreadyAddedSharedVolumes.includes(v.id)));
|
||||
} else {
|
||||
setShareableVolumes([]);
|
||||
toast.error('An error occurred while fetching shareable volumes');
|
||||
}
|
||||
setIsLoadingVolumes(false);
|
||||
});
|
||||
|
||||
@@ -108,6 +108,8 @@ export default function StorageEditDialog({ children, volume, app, nodesInfo }:
|
||||
});
|
||||
}, [volume]);
|
||||
|
||||
const values = form.watch();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div onClick={() => setIsOpen(true)}>
|
||||
@@ -154,6 +156,12 @@ export default function StorageEditDialog({ children, volume, app, nodesInfo }:
|
||||
)}
|
||||
/>
|
||||
|
||||
{volume && volume.size !== values.size && volume.shareWithOtherApps && <>
|
||||
<p className="text-sm text-yellow-600">
|
||||
When changing the size of a shared volume, ensure that all apps using this volume are shut down before deploying the changes.
|
||||
</p>
|
||||
</>}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="accessMode"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
|
||||
import { AppExtendedModel } from "@/shared/model/app-extended.model";
|
||||
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Download, EditIcon, Folder, TrashIcon, Share2 } from "lucide-react";
|
||||
import { Download, EditIcon, Folder, TrashIcon, Share2, Unlink2, Unlink } from "lucide-react";
|
||||
import DialogEditDialog from "./storage-edit-overlay";
|
||||
import SharedStorageEditDialog from "./shared-storage-edit-overlay";
|
||||
import { Toast } from "@/frontend/utils/toast.utils";
|
||||
@@ -47,7 +47,7 @@ export default function StorageList({ app, readonly, nodesInfo }: {
|
||||
if (response.status === 'success' && response.data) {
|
||||
const mappedVolumeData = [...app.appVolumes] as AppVolumeWithCapacity[];
|
||||
for (let item of mappedVolumeData) {
|
||||
const volume = response.data.find(x => x.pvcName === KubeObjectNameUtils.toPvcName(item.id));
|
||||
const volume = response.data.find(x => x.pvcName === KubeObjectNameUtils.toPvcName(item.sharedVolumeId || item.id));
|
||||
if (volume) {
|
||||
item.usedBytes = volume.usedBytes;
|
||||
item.capacityBytes = KubeSizeConverter.fromMegabytesToBytes(item.size);
|
||||
@@ -62,16 +62,17 @@ export default function StorageList({ app, readonly, nodesInfo }: {
|
||||
|
||||
React.useEffect(() => {
|
||||
loadAndMapStorageData();
|
||||
}, [app.appVolumes]);
|
||||
}, [app.appVolumes, app]);
|
||||
|
||||
const { openConfirmDialog: openDialog } = useConfirmDialog();
|
||||
|
||||
const asyncDeleteVolume = async (volumeId: string) => {
|
||||
const asyncDeleteVolume = async (volumeId: string, isBaseVolume: boolean) => {
|
||||
try {
|
||||
const confirm = await openDialog({
|
||||
title: "Delete Volume",
|
||||
description: "The volume will be removed and the Data will be lost. The changes will take effect, after you deploy the app. Are you sure you want to remove this volume?",
|
||||
okButton: "Delete Volume"
|
||||
title: isBaseVolume ? "Delete Volume" : "Detach Volume",
|
||||
description: isBaseVolume ? "The volume will be removed and the Data will be lost. The changes will take effect, after you deploy the app. Are you sure you want to remove this volume?" :
|
||||
"The volume will be detached from the app. The data will remain on the cluster and can be re-attached later. The changes will take effect, after you deploy the app. Are you sure you want to detach this volume?",
|
||||
okButton: isBaseVolume ? "Delete Volume" : "Detach Volume"
|
||||
});
|
||||
if (confirm) {
|
||||
setIsLoading(true);
|
||||
@@ -281,12 +282,12 @@ export default function StorageList({ app, readonly, nodesInfo }: {
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={200}>
|
||||
<TooltipTrigger>
|
||||
<Button variant="ghost" onClick={() => asyncDeleteVolume(volume.id)} disabled={isLoading}>
|
||||
<TrashIcon />
|
||||
<Button variant="ghost" onClick={() => asyncDeleteVolume(volume.id, !volume.sharedVolumeId)} disabled={isLoading}>
|
||||
{volume.sharedVolumeId ? <Unlink /> : <TrashIcon />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Delete volume</p>
|
||||
<p>{volume.sharedVolumeId ? 'Detach Volume' : 'Delete Volume'}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -298,7 +298,8 @@ class AppService {
|
||||
}
|
||||
});
|
||||
|
||||
if (existingAppWithSameVolumeMountPath.filter(x => x.id !== volumeToBeSaved.id)
|
||||
if (existingAppWithSameVolumeMountPath
|
||||
.filter(x => x.id !== volumeToBeSaved.id)
|
||||
.some(x => x.containerMountPath === volumeToBeSaved.containerMountPath)) {
|
||||
throw new ServiceException("Mount Path is already configured within the same app.");
|
||||
}
|
||||
@@ -329,12 +330,16 @@ class AppService {
|
||||
where: {
|
||||
id
|
||||
}, include: {
|
||||
app: true
|
||||
app: true,
|
||||
sharedVolumes: true
|
||||
}
|
||||
});
|
||||
if (!existingVolume) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get ids of all apps that use this volume as shared volume --> to reset cache
|
||||
let additionalAppIds = existingVolume.sharedVolumes.map(v => v.appId);
|
||||
try {
|
||||
await dataAccess.client.appVolume.delete({
|
||||
where: {
|
||||
@@ -344,6 +349,9 @@ class AppService {
|
||||
} finally {
|
||||
revalidateTag(Tags.app(existingVolume.appId));
|
||||
revalidateTag(Tags.apps(existingVolume.app.projectId));
|
||||
for (const appId of additionalAppIds) {
|
||||
revalidateTag(Tags.app(appId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import longhornApiAdapter from "../adapter/longhorn-api.adapter";
|
||||
import dataAccess from "../adapter/db.client";
|
||||
import pvcService from "./pvc.service";
|
||||
import { KubeObjectNameUtils } from "../utils/kube-object-name.utils";
|
||||
import appService from "./app.service";
|
||||
import projectService from "./project.service";
|
||||
import { AppMonitoringUsageModel } from "@/shared/model/app-monitoring-usage.model";
|
||||
|
||||
@@ -58,6 +57,7 @@ class MonitorService {
|
||||
mountPath: appVolume.containerMountPath,
|
||||
usedBytes: longhornVolume.actualSizeBytes,
|
||||
capacityBytes: KubeSizeConverter.fromMegabytesToBytes(baseVolume?.size ?? appVolume.size),
|
||||
isBaseVolume: !sharedVolumeId
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -100,13 +100,11 @@ class PvcService {
|
||||
}
|
||||
|
||||
async createPvcForVolumeIfNotExists(projectId: string, app: AppVolumeWithSharing) {
|
||||
const baseVolume = app.sharedVolumeId
|
||||
? await dataAccess.client.appVolume.findFirstOrThrow({
|
||||
where: {
|
||||
id: app.sharedVolumeId
|
||||
}
|
||||
})
|
||||
: app;
|
||||
const baseVolume = app.sharedVolumeId ? await dataAccess.client.appVolume.findFirstOrThrow({
|
||||
where: {
|
||||
id: app.sharedVolumeId
|
||||
}
|
||||
}) : app;
|
||||
const pvcName = KubeObjectNameUtils.toPvcName(baseVolume.id);
|
||||
const existingPvc = await this.getExistingPvcByVolumeId(projectId, baseVolume.id);
|
||||
|
||||
|
||||
@@ -5,5 +5,6 @@ export interface AppVolumeMonitoringUsageModel {
|
||||
appId: string,
|
||||
mountPath: string,
|
||||
usedBytes: number,
|
||||
capacityBytes: number
|
||||
capacityBytes: number,
|
||||
isBaseVolume: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user