This is an automated email from the ASF dual-hosted git repository.

DaanHoogland pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.22 by this push:
     new c7605686190 ui: label instance volume sizes as GiB (#13831)
c7605686190 is described below

commit c76056861905b163f9258b5ff5ed9186c707095e
Author: Brad <[email protected]>
AuthorDate: Wed Sep 2 09:09:48 2026 +0100

    ui: label instance volume sizes as GiB (#13831)
---
 ui/src/components/view/DetailsTab.vue            |  2 +-
 ui/src/components/view/InfoCard.vue              |  2 +-
 ui/src/components/view/VolumesTab.vue            |  2 +-
 ui/tests/unit/components/view/DetailsTab.spec.js | 67 ++++++++++++++++++++++++
 ui/tests/unit/components/view/InfoCard.spec.js   | 64 ++++++++++++++++++++++
 ui/tests/unit/components/view/VolumesTab.spec.js | 58 ++++++++++++++++++++
 6 files changed, 192 insertions(+), 3 deletions(-)

diff --git a/ui/src/components/view/DetailsTab.vue 
b/ui/src/components/view/DetailsTab.vue
index 135ea7384fa..ba9a43ac871 100644
--- a/ui/src/components/view/DetailsTab.vue
+++ b/ui/src/components/view/DetailsTab.vue
@@ -65,7 +65,7 @@
           <div v-else-if="$route.meta.name === 'backup' && item === 'volumes'">
             <div v-for="(volume, idx) in JSON.parse(dataResource[item])" 
:key="idx">
               <router-link v-if="!dataResource['vmbackupofferingremoved']" 
:to="{ path: '/volume/' + volume.uuid }">{{ volume.type }} - {{ volume.path 
}}</router-link>
-              <span v-else>{{ volume.type }} - {{ volume.path }}</span> ({{ 
parseFloat(volume.size / (1024.0 * 1024.0 * 1024.0)).toFixed(1) }} GB)
+              <span v-else>{{ volume.type }} - {{ volume.path }}</span> ({{ 
parseFloat(volume.size / (1024.0 * 1024.0 * 1024.0)).toFixed(1) }} GiB)
             </div>
           </div>
           <div v-else-if="$route.meta.name === 'computeoffering' && item === 
'rootdisksize'">
diff --git a/ui/src/components/view/InfoCard.vue 
b/ui/src/components/view/InfoCard.vue
index 0031d730f56..e37c8af3d6e 100644
--- a/ui/src/components/view/InfoCard.vue
+++ b/ui/src/components/view/InfoCard.vue
@@ -370,7 +370,7 @@
               <div class="resource-detail-item__label" v-else>{{ 
$t('label.disksize') }}</div>
               <div class="resource-detail-item__details">
                 <hdd-outlined />
-                <span style="width: 100%;" v-if="$route.meta.name === 'vm' && 
resource.volumes">{{ (resource.volumes.reduce((total, item) => total += 
item.size, 0) / (1024 * 1024 * 1024.0)).toFixed(2) }} GB Storage</span>
+                <span style="width: 100%;" v-if="$route.meta.name === 'vm' && 
resource.volumes">{{ (resource.volumes.reduce((total, item) => total += 
item.size, 0) / (1024 * 1024 * 1024.0)).toFixed(2) }} GiB Storage</span>
                 <span style="width: 100%;" v-else-if="$route.meta.name === 
'backup'">
                   {{ $bytesToHumanReadableSize(resource.size) }}
                   <a-tooltip placement="right">
diff --git a/ui/src/components/view/VolumesTab.vue 
b/ui/src/components/view/VolumesTab.vue
index bdd511ef1ec..8bb8c0bb88f 100644
--- a/ui/src/components/view/VolumesTab.vue
+++ b/ui/src/components/view/VolumesTab.vue
@@ -38,7 +38,7 @@
         <status :text="text ? text : ''" />{{ text }}
       </template>
       <template v-if="column.key === 'size'">
-        {{ parseFloat(record.size / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} 
GB
+        {{ parseFloat(record.size / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} 
GiB
       </template>
       <template v-if="column.key === 'storage'">
         <router-link v-if="record.storageid" :to="{ path: '/storagepool/' + 
encodeURIComponent(record.storageid) }">{{ text }}</router-link>
diff --git a/ui/tests/unit/components/view/DetailsTab.spec.js 
b/ui/tests/unit/components/view/DetailsTab.spec.js
new file mode 100644
index 00000000000..19e531d6f58
--- /dev/null
+++ b/ui/tests/unit/components/view/DetailsTab.spec.js
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { flushPromises } from '@vue/test-utils'
+
+import common from '../../../common'
+import DetailsTab from '@/components/view/DetailsTab.vue'
+
+const i18n = common.createMockI18n('en')
+
+describe('Components > View > DetailsTab.vue', () => {
+  beforeEach(() => {
+    jest.spyOn(console, 'warn').mockImplementation(() => {})
+  })
+
+  afterEach(() => {
+    jest.restoreAllMocks()
+  })
+
+  it('displays backup volume sizes in GiB when the API provides bytes', async 
() => {
+    const router = common.createMockRouter([{
+      path: '/backup/:id',
+      name: 'backup',
+      meta: { name: 'backup', details: ['volumes'] },
+      component: { template: '<div />' }
+    }])
+    await router.push('/backup/backup-1')
+    await router.isReady()
+
+    const wrapper = common.createFactory(DetailsTab, {
+      router,
+      i18n,
+      props: {
+        resource: {
+          volumes: JSON.stringify([{
+            uuid: 'volume-1',
+            type: 'ROOT',
+            path: 'root.qcow2',
+            size: 2 * 1024 * 1024 * 1024
+          }]),
+          vmbackupofferingremoved: true
+        }
+      }
+    })
+
+    await flushPromises()
+
+    expect(wrapper.text()).toContain('2.0 GiB')
+    expect(wrapper.text()).not.toContain('2.0 GB')
+
+    wrapper.unmount()
+  })
+})
diff --git a/ui/tests/unit/components/view/InfoCard.spec.js 
b/ui/tests/unit/components/view/InfoCard.spec.js
new file mode 100644
index 00000000000..9f09dc705a9
--- /dev/null
+++ b/ui/tests/unit/components/view/InfoCard.spec.js
@@ -0,0 +1,64 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { flushPromises } from '@vue/test-utils'
+
+import common from '../../../common'
+import InfoCard from '@/components/view/InfoCard.vue'
+
+const i18n = common.createMockI18n('en')
+
+describe('Components > View > InfoCard.vue', () => {
+  beforeEach(() => {
+    jest.spyOn(console, 'warn').mockImplementation(() => {})
+  })
+
+  afterEach(() => {
+    jest.restoreAllMocks()
+  })
+
+  it('displays VM volume totals in GiB when the API provides bytes', async () 
=> {
+    const router = common.createMockRouter([{
+      path: '/vm/:id',
+      name: 'vm',
+      meta: { name: 'vm' },
+      component: { template: '<div />' }
+    }])
+    await router.push('/vm/vm-1')
+    await router.isReady()
+
+    const wrapper = common.createFactory(InfoCard, {
+      router,
+      i18n,
+      store: common.createMockStore({ user: { apis: {} } }),
+      props: {
+        resource: {
+          name: 'test-vm',
+          vmtype: 'UserVm',
+          volumes: [{ size: 2 * 1024 * 1024 * 1024 }]
+        }
+      }
+    })
+
+    await flushPromises()
+
+    expect(wrapper.text()).toContain('2.00 GiB Storage')
+    expect(wrapper.text()).not.toContain('2.00 GB Storage')
+
+    wrapper.unmount()
+  })
+})
diff --git a/ui/tests/unit/components/view/VolumesTab.spec.js 
b/ui/tests/unit/components/view/VolumesTab.spec.js
new file mode 100644
index 00000000000..6c333453ffb
--- /dev/null
+++ b/ui/tests/unit/components/view/VolumesTab.spec.js
@@ -0,0 +1,58 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import { flushPromises } from '@vue/test-utils'
+
+import common from '../../../common'
+import VolumesTab from '@/components/view/VolumesTab.vue'
+
+const router = common.createMockRouter()
+const i18n = common.createMockI18n('en')
+
+describe('Components > View > VolumesTab.vue', () => {
+  beforeEach(() => {
+    jest.spyOn(console, 'warn').mockImplementation(() => {})
+  })
+
+  afterEach(() => {
+    jest.restoreAllMocks()
+  })
+
+  it('displays byte-based volume sizes in GiB', async () => {
+    const wrapper = common.createFactory(VolumesTab, {
+      router,
+      i18n,
+      props: {
+        resource: { id: 'vm-1' },
+        items: [{
+          id: 'volume-1',
+          name: 'Data volume',
+          state: 'Ready',
+          type: 'DATADISK',
+          size: 2 * 1024 * 1024 * 1024
+        }]
+      }
+    })
+
+    await flushPromises()
+
+    expect(wrapper.text()).toContain('2.00 GiB')
+    expect(wrapper.text()).not.toContain('2.00 GB')
+
+    wrapper.unmount()
+  })
+})

Reply via email to