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()
+ })
+})