diff --git a/package-lock.json b/package-lock.json
index dd0ffb929e33117ce344179a8befa93f58057531..ca2246fc358cd9863e4b02b6b734093086273c8e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -70,9 +70,9 @@
       }
     },
     "node_modules/@apidevtools/json-schema-ref-parser": {
-      "version": "11.6.0",
-      "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.6.0.tgz",
-      "integrity": "sha512-I+d5/XrazqY86/kGsmjVercjjJ+w6MVXJj7vnHfUgXzaoLJAl0/tPk2WXVpHUeRqHqyJ6AGkXBqx6Dc3wJkrCQ==",
+      "version": "11.6.1",
+      "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.6.1.tgz",
+      "integrity": "sha512-DxjgKBCoyReu4p5HMvpmgSOfRhhBcuf5V5soDDRgOTZMwsA4KSFzol1abFZgiCTE11L2kKGca5Md9GwDdXVBwQ==",
       "dev": true,
       "dependencies": {
         "@jsdevtools/ono": "^7.1.3",
@@ -2732,12 +2732,12 @@
       }
     },
     "node_modules/@vue/compiler-core": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.24.tgz",
-      "integrity": "sha512-vbW/tgbwJYj62N/Ww99x0zhFTkZDTcGh3uwJEuadZ/nF9/xuFMC4693P9r+3sxGXISABpDKvffY5ApH9pmdd1A==",
+      "version": "3.4.25",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.25.tgz",
+      "integrity": "sha512-Y2pLLopaElgWnMNolgG8w3C5nNUVev80L7hdQ5iIKPtMJvhVpG0zhnBG/g3UajJmZdvW0fktyZTotEHD1Srhbg==",
       "dependencies": {
         "@babel/parser": "^7.24.4",
-        "@vue/shared": "3.4.24",
+        "@vue/shared": "3.4.25",
         "entities": "^4.5.0",
         "estree-walker": "^2.0.2",
         "source-map-js": "^1.2.0"
@@ -2749,24 +2749,24 @@
       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
     "node_modules/@vue/compiler-dom": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.24.tgz",
-      "integrity": "sha512-4XgABML/4cNndVsQndG6BbGN7+EoisDwi3oXNovqL/4jdNhwvP8/rfRMTb6FxkxIxUUtg6AI1/qZvwfSjxJiWA==",
+      "version": "3.4.25",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.25.tgz",
+      "integrity": "sha512-Ugz5DusW57+HjllAugLci19NsDK+VyjGvmbB2TXaTcSlQxwL++2PETHx/+Qv6qFwNLzSt7HKepPe4DcTE3pBWg==",
       "dependencies": {
-        "@vue/compiler-core": "3.4.24",
-        "@vue/shared": "3.4.24"
+        "@vue/compiler-core": "3.4.25",
+        "@vue/shared": "3.4.25"
       }
     },
     "node_modules/@vue/compiler-sfc": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.24.tgz",
-      "integrity": "sha512-nRAlJUK02FTWfA2nuvNBAqsDZuERGFgxZ8sGH62XgFSvMxO2URblzulExsmj4gFZ8e+VAyDooU9oAoXfEDNxTA==",
+      "version": "3.4.25",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.25.tgz",
+      "integrity": "sha512-m7rryuqzIoQpOBZ18wKyq05IwL6qEpZxFZfRxlNYuIPDqywrXQxgUwLXIvoU72gs6cRdY6wHD0WVZIFE4OEaAQ==",
       "dependencies": {
         "@babel/parser": "^7.24.4",
-        "@vue/compiler-core": "3.4.24",
-        "@vue/compiler-dom": "3.4.24",
-        "@vue/compiler-ssr": "3.4.24",
-        "@vue/shared": "3.4.24",
+        "@vue/compiler-core": "3.4.25",
+        "@vue/compiler-dom": "3.4.25",
+        "@vue/compiler-ssr": "3.4.25",
+        "@vue/shared": "3.4.25",
         "estree-walker": "^2.0.2",
         "magic-string": "^0.30.10",
         "postcss": "^8.4.38",
@@ -2787,12 +2787,12 @@
       }
     },
     "node_modules/@vue/compiler-ssr": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.24.tgz",
-      "integrity": "sha512-ZsAtr4fhaUFnVcDqwW3bYCSDwq+9Gk69q2r/7dAHDrOMw41kylaMgOP4zRnn6GIEJkQznKgrMOGPMFnLB52RbQ==",
+      "version": "3.4.25",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.25.tgz",
+      "integrity": "sha512-H2ohvM/Pf6LelGxDBnfbbXFPyM4NE3hrw0e/EpwuSiYu8c819wx+SVGdJ65p/sFrYDd6OnSDxN1MB2mN07hRSQ==",
       "dependencies": {
-        "@vue/compiler-dom": "3.4.24",
-        "@vue/shared": "3.4.24"
+        "@vue/compiler-dom": "3.4.25",
+        "@vue/shared": "3.4.25"
       }
     },
     "node_modules/@vue/devtools-api": {
@@ -2862,48 +2862,48 @@
       }
     },
     "node_modules/@vue/reactivity": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.24.tgz",
-      "integrity": "sha512-nup3fSYg4i4LtNvu9slF/HF/0dkMQYfepUdORBcMSsankzRPzE7ypAFurpwyRBfU1i7Dn1kcwpYsE1wETSh91g==",
+      "version": "3.4.25",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.25.tgz",
+      "integrity": "sha512-mKbEtKr1iTxZkAG3vm3BtKHAOhuI4zzsVcN0epDldU/THsrvfXRKzq+lZnjczZGnTdh3ojd86/WrP+u9M51pWQ==",
       "dependencies": {
-        "@vue/shared": "3.4.24"
+        "@vue/shared": "3.4.25"
       }
     },
     "node_modules/@vue/runtime-core": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.24.tgz",
-      "integrity": "sha512-c7iMfj6cJMeAG3s5yOn9Rc5D9e2/wIuaozmGf/ICGCY3KV5H7mbTVdvEkd4ZshTq7RUZqj2k7LMJWVx+EBiY1g==",
+      "version": "3.4.25",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.25.tgz",
+      "integrity": "sha512-3qhsTqbEh8BMH3pXf009epCI5E7bKu28fJLi9O6W+ZGt/6xgSfMuGPqa5HRbUxLoehTNp5uWvzCr60KuiRIL0Q==",
       "dependencies": {
-        "@vue/reactivity": "3.4.24",
-        "@vue/shared": "3.4.24"
+        "@vue/reactivity": "3.4.25",
+        "@vue/shared": "3.4.25"
       }
     },
     "node_modules/@vue/runtime-dom": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.24.tgz",
-      "integrity": "sha512-uXKzuh/Emfad2Y7Qm0ABsLZZV6H3mAJ5ZVqmAOlrNQRf+T5mxpPGZBfec1hkP41t6h6FwF6RSGCs/gd8WbuySQ==",
+      "version": "3.4.25",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.25.tgz",
+      "integrity": "sha512-ode0sj77kuwXwSc+2Yhk8JMHZh1sZp9F/51wdBiz3KGaWltbKtdihlJFhQG4H6AY+A06zzeMLkq6qu8uDSsaoA==",
       "dependencies": {
-        "@vue/runtime-core": "3.4.24",
-        "@vue/shared": "3.4.24",
+        "@vue/runtime-core": "3.4.25",
+        "@vue/shared": "3.4.25",
         "csstype": "^3.1.3"
       }
     },
     "node_modules/@vue/server-renderer": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.24.tgz",
-      "integrity": "sha512-H+DLK4sQF6sRgzKyofmlEVBIV/9KrQU6HIV7nt6yIwSGGKvSwlV8pqJlebUKLpbXaNHugdSfAbP6YmXF69lxow==",
+      "version": "3.4.25",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.25.tgz",
+      "integrity": "sha512-8VTwq0Zcu3K4dWV0jOwIVINESE/gha3ifYCOKEhxOj6MEl5K5y8J8clQncTcDhKF+9U765nRw4UdUEXvrGhyVQ==",
       "dependencies": {
-        "@vue/compiler-ssr": "3.4.24",
-        "@vue/shared": "3.4.24"
+        "@vue/compiler-ssr": "3.4.25",
+        "@vue/shared": "3.4.25"
       },
       "peerDependencies": {
-        "vue": "3.4.24"
+        "vue": "3.4.25"
       }
     },
     "node_modules/@vue/shared": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.24.tgz",
-      "integrity": "sha512-BW4tajrJBM9AGAknnyEw5tO2xTmnqgup0VTnDAMcxYmqOX0RG0b9aSUGAbEKolD91tdwpA6oCwbltoJoNzpItw=="
+      "version": "3.4.25",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.25.tgz",
+      "integrity": "sha512-k0yappJ77g2+KNrIaF0FFnzwLvUBLUYr8VOwz+/6vLsmItFp51AcxLL7Ey3iPd7BIRyWPOcqUjMnm7OkahXllA=="
     },
     "node_modules/@vue/tsconfig": {
       "version": "0.5.1",
@@ -6695,15 +6695,15 @@
       }
     },
     "node_modules/vue": {
-      "version": "3.4.24",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.24.tgz",
-      "integrity": "sha512-NPdx7dLGyHmKHGRRU5bMRYVE+rechR+KDU5R2tSTNG36PuMwbfAJ+amEvOAw7BPfZp5sQulNELSLm5YUkau+Sg==",
-      "dependencies": {
-        "@vue/compiler-dom": "3.4.24",
-        "@vue/compiler-sfc": "3.4.24",
-        "@vue/runtime-dom": "3.4.24",
-        "@vue/server-renderer": "3.4.24",
-        "@vue/shared": "3.4.24"
+      "version": "3.4.25",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.25.tgz",
+      "integrity": "sha512-HWyDqoBHMgav/OKiYA2ZQg+kjfMgLt/T0vg4cbIF7JbXAjDexRf5JRg+PWAfrAkSmTd2I8aPSXtooBFWHB98cg==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.4.25",
+        "@vue/compiler-sfc": "3.4.25",
+        "@vue/runtime-dom": "3.4.25",
+        "@vue/server-renderer": "3.4.25",
+        "@vue/shared": "3.4.25"
       },
       "peerDependencies": {
         "typescript": "*"
diff --git a/src/components/AppHeader.vue b/src/components/AppHeader.vue
index 857988fced70fd30e3578f372738d229da21f9ca..afc133156a816dd1a7636193d48b5483f37030fa 100644
--- a/src/components/AppHeader.vue
+++ b/src/components/AppHeader.vue
@@ -101,6 +101,13 @@ watch(
                   >S3 Bucket Keys
                 </router-link>
               </li>
+              <li>
+                <router-link
+                  class="dropdown-item"
+                  :to="{ name: 's3_multipart-uploads' }"
+                  >Multipart Uploads
+                </router-link>
+              </li>
             </ul>
           </li>
           <li class="nav-item dropdown">
diff --git a/src/components/object-storage/BucketListItem.vue b/src/components/object-storage/BucketListItem.vue
index e27f9906282f234c8cfbaaff8cc8c82d02e4ab2b..ce41b994c98d7462643b567838d066d90471a2d2 100644
--- a/src/components/object-storage/BucketListItem.vue
+++ b/src/components/object-storage/BucketListItem.vue
@@ -89,23 +89,24 @@ function toggleBucketPublicState() {
 onMounted(() => {
   if (!props.loading) {
     new Tooltip("#tooltip-" + randomIDSuffix);
-    new Tooltip("#ownBucketIcon-" + randomIDSuffix);
-    new Tooltip("#sharedBucketIcon-" + randomIDSuffix);
-    successToast = new Toast("#success-public-bucket-" + randomIDSuffix);
-    errorToast = new Toast("#error-public-bucket-" + randomIDSuffix);
+    new Tooltip(`#own-bucket-icon-${randomIDSuffix}`);
+    new Tooltip(`#shared-bucket-icon-${randomIDSuffix}`);
+    new Tooltip(`#public-bucket-icon-${randomIDSuffix}`);
+    successToast = new Toast(`#success-public-bucket-${randomIDSuffix}`);
+    errorToast = new Toast(`#error-public-bucket-${randomIDSuffix}`);
   }
 });
 </script>
 
 <template>
   <bootstrap-toast
-    :toast-id="'success-public-bucket-' + randomIDSuffix"
+    :toast-id="`success-public-bucket-${randomIDSuffix}`"
     v-if="!loading"
   >
     Bucket {{ bucket.name }} is now {{ bucket.public ? "public" : "private" }}
   </bootstrap-toast>
   <bootstrap-toast
-    :toast-id="'error-public-bucket-' + randomIDSuffix"
+    :toast-id="`error-public-bucket-${randomIDSuffix}`"
     color-class="danger"
     v-if="!loading"
   >
@@ -127,7 +128,7 @@ onMounted(() => {
   />
   <bucket-detail-modal
     v-if="props.active"
-    :modalId="'view-bucket-details-modal' + randomIDSuffix"
+    :modalId="`view-bucket-details-modal-${randomIDSuffix}`"
     :bucket="props.bucket"
     :edit-user-permission="permission"
   />
@@ -157,9 +158,17 @@ onMounted(() => {
           {{ bucket.name }}
         </span>
         <div class="text-nowrap">
+          <font-awesome-icon
+            :hidden="!bucket.public"
+            :id="`public-bucket-icon-${randomIDSuffix}`"
+            icon="fa-solid fa-earth-americas"
+            class="me-2"
+            data-bs-toogle="tooltip"
+            data-bs-title="Public Bucket"
+          />
           <font-awesome-icon
             :hidden="bucket.owner_id !== userRepository.currentUID"
-            :id="'ownBucketIcon-' + randomIDSuffix"
+            :id="`own-bucket-icon-${randomIDSuffix}`"
             icon="fa-solid fa-user"
             :class="{
               'me-2':
@@ -179,7 +188,7 @@ onMounted(() => {
               (permissionRepository.bucketPermissionsMapping[bucket.name] ?? [])
                 .length === 0
             "
-            :id="'sharedBucketIcon-' + randomIDSuffix"
+            :id="`shared-bucket-icon-${randomIDSuffix}`"
             icon="fa-solid fa-users"
             :class="{ 'me-2': props.active }"
             data-bs-toogle="tooltip"
@@ -194,7 +203,7 @@ onMounted(() => {
           <font-awesome-icon
             class="info-icon"
             data-bs-toggle="modal"
-            :data-bs-target="'#view-bucket-details-modal' + randomIDSuffix"
+            :data-bs-target="`#view-bucket-details-modal-${randomIDSuffix}`"
             v-if="props.active"
             icon="fa-solid fa-circle-info"
           />
diff --git a/src/components/object-storage/modals/CopyObjectModal.vue b/src/components/object-storage/modals/CopyObjectModal.vue
index d03cfcbae1e64e562c59dfebb3fc5cffd4b47166..69b6c921eb21fab5913ceabe9b3a6640fcac1c23 100644
--- a/src/components/object-storage/modals/CopyObjectModal.vue
+++ b/src/components/object-storage/modals/CopyObjectModal.vue
@@ -135,7 +135,9 @@ onMounted(() => {
               >
                 <option disabled selected>Select one...</option>
                 <option
-                  v-for="bucket in bucketRepository.writableBuckets"
+                  v-for="bucket in bucketRepository.buckets.filter((b) =>
+                    bucketRepository.writableBucket(b.name),
+                  )"
                   :key="bucket.name"
                   :value="bucket.name"
                 >
diff --git a/src/components/parameter-schema/form-mode/ParameterFileInput.vue b/src/components/parameter-schema/form-mode/ParameterFileInput.vue
index 4bb6fe77ba714515b6f8175b42402ddee0364dec..6b76c7ae05dd812afbb77e5691d438673704d368 100644
--- a/src/components/parameter-schema/form-mode/ParameterFileInput.vue
+++ b/src/components/parameter-schema/form-mode/ParameterFileInput.vue
@@ -178,11 +178,17 @@ onMounted(() => {
   >
     <option selected disabled value="">Please select a bucket</option>
     <option
-      v-for="bucket in bucketRepository.ownBucketsAndFullPermissions"
-      :key="bucket"
-      :value="bucket"
+      v-for="bucket in bucketRepository.buckets"
+      :key="bucket.name"
+      :value="bucket.name"
     >
-      {{ bucket }}
+      {{ bucket.name }}
+      <template v-if="!bucketRepository.writableBucket(bucket.name)"
+        >(read-only)
+      </template>
+      <template v-if="!bucketRepository.readableBucket(bucket.name)"
+        >(write-only)
+      </template>
     </option>
   </select>
   <input
diff --git a/src/router/s3Routes.ts b/src/router/s3Routes.ts
index bc7c73b29c5dc703d284e2bcd21a6b0c964adac9..9ec2a42967d2b38ebde016b69d6ade39811c2ea5 100644
--- a/src/router/s3Routes.ts
+++ b/src/router/s3Routes.ts
@@ -28,4 +28,12 @@ export const s3Routes: RouteRecordRaw[] = [
     },
     component: () => import("../views/object-storage/S3KeysView.vue"),
   },
+  {
+    path: "object-storage/multipart-uploads",
+    name: "s3_multipart-uploads",
+    meta: {
+      title: "Multipart Uploads",
+    },
+    component: () => import("../views/object-storage/MultiPartUploadsView.vue"),
+  },
 ];
diff --git a/src/stores/buckets.ts b/src/stores/buckets.ts
index 53ada471028e68102c4666d224c551dd06c396ad..46911174c735c67e20fa3777bd8f45386442837c 100644
--- a/src/stores/buckets.ts
+++ b/src/stores/buckets.ts
@@ -3,7 +3,6 @@ import {
   BucketPermissionService,
   BucketService,
   BucketType,
-  Permission,
 } from "@/client/s3proxy";
 import type {
   BucketOut,
@@ -34,61 +33,44 @@ export const useBucketStore = defineStore({
       });
       return tempList;
     },
-    ownBucketsAndFullPermissions(): string[] {
+    ownBuckets(): BucketOut[] {
       const authStore = useAuthStore();
-      const names = this.buckets
-        .filter((bucket) => bucket.owner_id === authStore.currentUID)
-        .map((bucket) => bucket.name)
-        .concat(
-          Object.values(this.ownPermissions)
-            .filter((perm) => perm.permission === Permission.READWRITE)
-            .map((perm) => perm.bucket_name),
-        );
-      names.sort();
-      return names;
+      return this.buckets.filter(
+        (bucket) => bucket.owner_id === authStore.currentUID,
+      );
     },
     getBucketPermissions(): (bucketName: string) => BucketPermissionOut[] {
       return (bucketName) => this.bucketPermissionsMapping[bucketName] ?? [];
     },
     permissionFeatureAllowed(): (bucketName: string) => boolean {
+      const authStore = useAuthStore();
       return (bucketName) => {
         // If a permission for the bucket exist, then false
         if (this.ownPermissions[bucketName] != undefined) {
           return false;
         }
         // If the bucket doesn't exist, then false
-        return this.bucketMapping[bucketName] != undefined;
+        return this.bucketMapping[bucketName]?.owner_id == authStore.currentUID;
       };
     },
-    writableBuckets(): BucketOut[] {
-      return this.buckets.filter((bucket) => {
-        if (this.ownPermissions[bucket.name] != undefined) {
-          return this.ownPermissions[bucket.name].permission !== "READ";
-        }
-        return true;
-      });
-    },
     writableBucket(): (bucketName: string) => boolean {
+      const authStore = useAuthStore();
       return (bucketName) => {
         // If this is a foreign bucket, check that the user has write permission
         if (this.ownPermissions[bucketName] != undefined) {
           return this.ownPermissions[bucketName].permission !== "READ";
         }
-        // If the bucket doesn't exist, then false
-        return this.bucketMapping[bucketName] != undefined;
+        return this.bucketMapping[bucketName]?.owner_id == authStore.currentUID;
       };
     },
     readableBucket(): (bucketName: string) => boolean {
+      const authStore = useAuthStore();
       return (bucketName) => {
         // If this is a foreign bucket, check that the user has read permission
         if (this.ownPermissions[bucketName] != undefined) {
           return this.ownPermissions[bucketName].permission !== "WRITE";
         }
-        // If the user owns the bucket, check the bucket constraint
-        if (this.bucketMapping[bucketName] != null) {
-          return true;
-        }
-        return false;
+        return this.bucketMapping[bucketName]?.owner_id == authStore.currentUID;
       };
     },
   },
@@ -134,7 +116,7 @@ export const useBucketStore = defineStore({
       delete this.bucketMapping[bucketName];
     },
     fetchOwnBuckets(onFinally?: () => void): Promise<BucketOut[]> {
-      if (this.buckets.length > 0) {
+      if (this.ownBuckets.length > 0) {
         onFinally?.();
       }
       const userStore = useAuthStore();
diff --git a/src/stores/s3objects.ts b/src/stores/s3objects.ts
index 149a3eb0bf89f6ffab861d0606b89812d09342a1..807218393a7a6527f0a9cd4982bc6ccaf055f365 100644
--- a/src/stores/s3objects.ts
+++ b/src/stores/s3objects.ts
@@ -1,9 +1,15 @@
 import { defineStore } from "pinia";
-import type { _Object as S3Object, HeadObjectOutput } from "@aws-sdk/client-s3";
+import type {
+  _Object as S3Object,
+  HeadObjectOutput,
+  MultipartUpload,
+} from "@aws-sdk/client-s3";
 import {
+  AbortMultipartUploadCommand,
   CopyObjectCommand,
   GetObjectCommand,
   HeadObjectCommand,
+  ListMultipartUploadsCommand,
   PutObjectCommand,
   S3Client,
 } from "@aws-sdk/client-s3";
@@ -20,6 +26,7 @@ import dayjs from "dayjs";
 import { Upload } from "@aws-sdk/lib-storage";
 import type { Progress } from "@aws-sdk/lib-storage";
 import type { AbortController } from "@smithy/types";
+import { useAuthStore } from "@/stores/users";
 
 export const useS3ObjectStore = defineStore({
   id: "s3objects",
@@ -27,6 +34,7 @@ export const useS3ObjectStore = defineStore({
     ({
       objectMapping: {},
       objectMetaMapping: {},
+      multiPartUploadsMapping: {},
       client: new S3Client({
         region: "us-east-1",
         endpoint: environment.S3_URL,
@@ -39,6 +47,7 @@ export const useS3ObjectStore = defineStore({
     }) as {
       objectMapping: Record<string, S3Object[]>;
       objectMetaMapping: Record<string, HeadObjectOutput>;
+      multiPartUploadsMapping: Record<string, MultipartUpload[]>;
       client: S3Client;
     },
   getters: {
@@ -96,6 +105,28 @@ export const useS3ObjectStore = defineStore({
         },
       });
     },
+    fetchMultipartUploads(bucketName: string): Promise<MultipartUpload[]> {
+      const listCommand = new ListMultipartUploadsCommand({
+        Bucket: bucketName,
+      });
+      return this.client
+        .send(listCommand)
+        .then((response) => response.Uploads ?? [])
+        .then((uploads) => {
+          this.multiPartUploadsMapping[bucketName] = uploads;
+          const userRepository = useAuthStore();
+          userRepository.fetchUsernames(
+            uploads
+              .map((upload) => upload.Initiator?.ID ?? "")
+              .filter(
+                (initiator) =>
+                  initiator.length > 0 &&
+                  initiator != userRepository.currentUID,
+              ),
+          );
+          return uploads;
+        });
+    },
     async fetchS3Objects(
       bucketName: string,
       prefix?: string,
@@ -237,7 +268,23 @@ export const useS3ObjectStore = defineStore({
       if (onProgress != undefined) {
         parallelUploads3.on("httpUploadProgress", onProgress);
       }
-      await parallelUploads3.done();
+      try {
+        await parallelUploads3.done();
+      } catch (e) {
+        // if there is an error with the multipart upload, send an abort multipart upload command
+        const uploadObject = JSON.parse(JSON.stringify(parallelUploads3));
+        if (uploadObject["isMultiPart"] && uploadObject["uploadId"]) {
+          await this.client.send(
+            new AbortMultipartUploadCommand({
+              // AbortMultipartUploadRequest
+              Bucket: bucketName, // required
+              Key: key, // required
+              UploadId: uploadObject["uploadId"], // required
+            }),
+          );
+        }
+        throw e;
+      }
       const newObj = {
         Key: key,
         Size: file.size ?? 0,
@@ -263,5 +310,24 @@ export const useS3ObjectStore = defineStore({
         return newObj;
       });
     },
+    abortMultipartUpload(
+      bucketName: string,
+      key: string,
+      uploadId: string,
+    ): Promise<void> {
+      const cmd = new AbortMultipartUploadCommand({
+        Bucket: bucketName,
+        Key: key,
+        UploadId: uploadId,
+      });
+      return this.client.send(cmd).then(() => {
+        const index = this.multiPartUploadsMapping[bucketName]?.findIndex(
+          (upload) => upload.UploadId == uploadId,
+        );
+        if (index != undefined && index > -1) {
+          this.multiPartUploadsMapping[bucketName]?.splice(index, 1);
+        }
+      });
+    },
   },
 });
diff --git a/src/views/object-storage/MultiPartUploadsView.vue b/src/views/object-storage/MultiPartUploadsView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9fe9132c468f61c5ce2679ed6461430032788fdf
--- /dev/null
+++ b/src/views/object-storage/MultiPartUploadsView.vue
@@ -0,0 +1,199 @@
+<script setup lang="ts">
+import { computed, onMounted, reactive } from "vue";
+import { useS3ObjectStore } from "@/stores/s3objects";
+import { useBucketStore } from "@/stores/buckets";
+import { useNameStore } from "@/stores/names";
+import dayjs from "dayjs";
+import { useAuthStore } from "@/stores/users";
+import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
+import { Tooltip } from "bootstrap";
+import { useS3KeyStore } from "@/stores/s3keys";
+
+const objectRepository = useS3ObjectStore();
+const bucketRepository = useBucketStore();
+const nameRepository = useNameStore();
+const userRepository = useAuthStore();
+const s3keyRepository = useS3KeyStore();
+
+let refreshTimeout: NodeJS.Timeout | undefined = undefined;
+
+const uploadState = reactive<{
+  loading: boolean;
+  initialFetched: boolean;
+}>({
+  loading: true,
+  initialFetched: false,
+});
+
+function abortMultipartUpload(
+  bucketName: string,
+  key?: string,
+  uploadId?: string,
+) {
+  if (uploadId && key) {
+    objectRepository.abortMultipartUpload(bucketName, key, uploadId).then();
+  }
+}
+
+const uploadsInOwnBucketsPresent = computed<boolean>(() => {
+  for (const entries of Object.entries(
+    objectRepository.multiPartUploadsMapping,
+  )) {
+    const bucketName = entries[0];
+    const uploads = entries[1];
+    if (
+      uploads.length > 0 &&
+      bucketRepository.bucketMapping[bucketName]?.owner_id ==
+        userRepository.currentUID
+    ) {
+      return true;
+    }
+  }
+  return false;
+});
+
+function refreshMultipartUploads() {
+  clearTimeout(refreshTimeout);
+  refreshTimeout = setTimeout(() => {
+    fetchMultipartUploads();
+  }, 500);
+}
+
+function fetchMultipartUploads() {
+  Promise.all(
+    bucketRepository.ownBuckets.map((bucket) =>
+      objectRepository.fetchMultipartUploads(bucket.name),
+    ),
+  ).finally(() => {
+    uploadState.loading = false;
+  });
+}
+
+onMounted(() => {
+  // if uploads where already fetched, then don't show a loading screen
+  if (Object.keys(objectRepository.multiPartUploadsMapping).length > 0) {
+    uploadState.loading = false;
+  }
+  // First fetch s3 keys and then fetch uploads
+  s3keyRepository.fetchS3Keys(() => {
+    if (!uploadState.initialFetched) {
+      uploadState.initialFetched = true;
+      if (bucketRepository.ownBuckets.length > 0) {
+        fetchMultipartUploads();
+      } else {
+        bucketRepository.fetchOwnBuckets().finally(fetchMultipartUploads);
+      }
+    }
+  });
+  Tooltip.getOrCreateInstance("#refreshUploadsButton");
+});
+</script>
+
+<template>
+  <div
+    class="row border-bottom mb-4 pb-2 justify-content-between align-items-center"
+  >
+    <h2 class="w-fit mb-0">Multipart Uploads</h2>
+    <div class="w-fit">
+      <button
+        class="btn btn-light me-2 shadow-sm border"
+        @click="refreshMultipartUploads"
+        id="refreshUploadsButton"
+        data-bs-title="Refresh Multipart Uploads"
+        data-bs-toggle="tooltip"
+      >
+        <font-awesome-icon icon="fa-solid fa-arrow-rotate-right" />
+        <span class="visually-hidden">Refresh Data Buckets</span>
+      </button>
+    </div>
+  </div>
+  <p>
+    When uploading large files to S3, a multipart upload process is utilized.
+    This involves breaking the file into smaller chunks, which are then uploaded
+    asynchronously. While these uploaded parts contribute to the bucket quotas,
+    they are not displayed when listing all objects in a bucket. If a multipart
+    upload is left incomplete or not properly aborted, these chunks can
+    accumulate in your bucket without your awareness. However, aborting the
+    erroneous multipart upload will release the occupied space.
+  </p>
+  <div v-if="uploadState.loading" class="text-center mt-5">
+    <div class="spinner-border" role="status" style="width: 3rem; height: 3rem">
+      <span class="visually-hidden">Loading...</span>
+    </div>
+  </div>
+  <div class="accordion mt-2" v-else-if="uploadsInOwnBucketsPresent">
+    <template v-for="bucket in bucketRepository.ownBuckets" :key="bucket">
+      <div
+        class="accordion-item"
+        v-if="
+          objectRepository.multiPartUploadsMapping[bucket.name] &&
+          objectRepository.multiPartUploadsMapping[bucket.name].length > 0
+        "
+      >
+        <h2 class="accordion-header">
+          <button
+            class="accordion-button"
+            type="button"
+            data-bs-toggle="collapse"
+            :data-bs-target="`#collapse-${bucket.name}`"
+            aria-expanded="true"
+            :aria-controls="`#collapse-${bucket.name}`"
+          >
+            Bucket: {{ bucket.name }}
+          </button>
+        </h2>
+        <div
+          :id="`collapse-${bucket.name}`"
+          class="accordion-collapse collapse show"
+        >
+          <div class="accordion-body">
+            <table class="table table-striped align-middle">
+              <thead>
+                <tr>
+                  <th scope="col">Key</th>
+                  <th scope="col">Initiator</th>
+                  <th scope="col">Initiated</th>
+                  <th></th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr
+                  v-for="upload in objectRepository.multiPartUploadsMapping[
+                    bucket.name
+                  ]"
+                  :key="upload.UploadId"
+                >
+                  <th scope="row">{{ upload.Key }}</th>
+                  <td>{{ nameRepository.getName(upload.Initiator?.ID) }}</td>
+                  <td>
+                    {{ dayjs(upload.Initiated).format("YYYY-MM-DD HH:mm:ss") }}
+                  </td>
+                  <th class="text-end">
+                    <button
+                      type="button"
+                      class="btn btn-danger btn-sm"
+                      @click="
+                        abortMultipartUpload(
+                          bucket.name,
+                          upload.Key,
+                          upload.UploadId,
+                        )
+                      "
+                    >
+                      Abort
+                    </button>
+                  </th>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+    </template>
+  </div>
+  <div v-else class="text-center fst-italic fs-5 mt-5">
+    No multipart uploads
+  </div>
+</template>
+
+<style scoped></style>