diff --git a/src/app/monitoring/app-volumes-monitoring.tsx b/src/app/monitoring/app-volumes-monitoring.tsx
index e628af5..bad7d9d 100644
--- a/src/app/monitoring/app-volumes-monitoring.tsx
+++ b/src/app/monitoring/app-volumes-monitoring.tsx
@@ -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) {
diff --git a/src/app/monitoring/page.tsx b/src/app/monitoring/page.tsx
index ed7286f..e46dc0e 100644
--- a/src/app/monitoring/page.tsx
+++ b/src/app/monitoring/page.tsx
@@ -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 (
diff --git a/src/app/project/app/[appId]/volumes/shared-storage-edit-overlay.tsx b/src/app/project/app/[appId]/volumes/shared-storage-edit-overlay.tsx
index ef51964..9f5d4da 100644
--- a/src/app/project/app/[appId]/volumes/shared-storage-edit-overlay.tsx
+++ b/src/app/project/app/[appId]/volumes/shared-storage-edit-overlay.tsx
@@ -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);
});
diff --git a/src/app/project/app/[appId]/volumes/storage-edit-overlay.tsx b/src/app/project/app/[appId]/volumes/storage-edit-overlay.tsx
index adc07ab..db83818 100644
--- a/src/app/project/app/[appId]/volumes/storage-edit-overlay.tsx
+++ b/src/app/project/app/[appId]/volumes/storage-edit-overlay.tsx
@@ -108,6 +108,8 @@ export default function StorageEditDialog({ children, volume, app, nodesInfo }:
});
}, [volume]);
+ const values = form.watch();
+
return (
<>
setIsOpen(true)}>
@@ -154,6 +156,12 @@ export default function StorageEditDialog({ children, volume, app, nodesInfo }:
)}
/>
+ {volume && volume.size !== values.size && volume.shareWithOtherApps && <>
+
+ When changing the size of a shared volume, ensure that all apps using this volume are shut down before deploying the changes.
+
+ >}
+
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 }: {
- asyncDeleteVolume(volume.id)} disabled={isLoading}>
-
+ asyncDeleteVolume(volume.id, !volume.sharedVolumeId)} disabled={isLoading}>
+ {volume.sharedVolumeId ? : }
- Delete volume
+ {volume.sharedVolumeId ? 'Detach Volume' : 'Delete Volume'}
diff --git a/src/server/services/app.service.ts b/src/server/services/app.service.ts
index fb2e351..008b956 100644
--- a/src/server/services/app.service.ts
+++ b/src/server/services/app.service.ts
@@ -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));
+ }
}
}
diff --git a/src/server/services/monitoring.service.ts b/src/server/services/monitoring.service.ts
index b2bf4da..4c1588d 100644
--- a/src/server/services/monitoring.service.ts
+++ b/src/server/services/monitoring.service.ts
@@ -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
});
}
diff --git a/src/server/services/pvc.service.ts b/src/server/services/pvc.service.ts
index a80bf62..1af4711 100644
--- a/src/server/services/pvc.service.ts
+++ b/src/server/services/pvc.service.ts
@@ -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);
diff --git a/src/shared/model/app-volume-monitoring-usage.model.ts b/src/shared/model/app-volume-monitoring-usage.model.ts
index 238aa1b..418036d 100644
--- a/src/shared/model/app-volume-monitoring-usage.model.ts
+++ b/src/shared/model/app-volume-monitoring-usage.model.ts
@@ -5,5 +5,6 @@ export interface AppVolumeMonitoringUsageModel {
appId: string,
mountPath: string,
usedBytes: number,
- capacityBytes: number
+ capacityBytes: number,
+ isBaseVolume: boolean
}