This is an automated email from the ASF dual-hosted git repository. joao pushed a commit to branch 4.19 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.19 by this push: new 49cd5ba64ad Fix link to removed volumes being shown in info card and list view (#8833) 49cd5ba64ad is described below commit 49cd5ba64ad6f3a145f03592903d07a8dffa4578 Author: Fabricio Duarte <fabricio.duarte...@gmail.com> AuthorDate: Wed Jul 24 09:09:07 2024 -0300 Fix link to removed volumes being shown in info card and list view (#8833) * Framework for validating links in the front-end * Rename valid links map in the list view --- .../org/apache/cloudstack/api/ApiConstants.java | 1 + .../cloudstack/api/response/SnapshotResponse.java | 8 +++++ .../META-INF/db/views/cloud.snapshot_view.sql | 1 + .../main/java/com/cloud/api/ApiResponseHelper.java | 1 + .../cloud/api/query/dao/SnapshotJoinDaoImpl.java | 1 + .../com/cloud/api/query/vo/SnapshotJoinVO.java | 8 +++++ ui/src/components/view/InfoCard.vue | 8 +++-- ui/src/components/view/ListView.vue | 16 +++++++++- ui/src/utils/links.js | 36 ++++++++++++++++++++++ 9 files changed, 77 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 2324b861830..d6099ac4717 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -576,6 +576,7 @@ public class ApiConstants { public static final String AGGREGATE_NAME = "aggregatename"; public static final String POOL_NAME = "poolname"; public static final String VOLUME_NAME = "volumename"; + public static final String VOLUME_STATE = "volumestate"; public static final String SNAPSHOT_POLICY = "snapshotpolicy"; public static final String SNAPSHOT_RESERVATION = "snapshotreservation"; public static final String IP_NETWORK_LIST = "iptonetworklist"; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java index e160f64ebe9..02132416b8a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java @@ -71,6 +71,10 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements @Param(description = "type of the disk volume") private String volumeType; + @SerializedName(ApiConstants.VOLUME_STATE) + @Param(description = "state of the disk volume") + private String volumeState; + @SerializedName(ApiConstants.CREATED) @Param(description = " the date the snapshot was created") private Date created; @@ -199,6 +203,10 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements this.volumeType = volumeType; } + public void setVolumeState(String volumeState) { + this.volumeState = volumeState; + } + public void setCreated(Date created) { this.created = created; } diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.snapshot_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.snapshot_view.sql index c6b8d6b4d05..d0eddc1fc4b 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.snapshot_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.snapshot_view.sql @@ -48,6 +48,7 @@ SELECT `volumes`.`uuid` AS `volume_uuid`, `volumes`.`name` AS `volume_name`, `volumes`.`volume_type` AS `volume_type`, + `volumes`.`state` AS `volume_state`, `volumes`.`size` AS `volume_size`, `data_center`.`id` AS `data_center_id`, `data_center`.`uuid` AS `data_center_uuid`, diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index e801d1f9b31..eec767d7b5e 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -659,6 +659,7 @@ public class ApiResponseHelper implements ResponseGenerator { snapshotResponse.setVolumeId(volume.getUuid()); snapshotResponse.setVolumeName(volume.getName()); snapshotResponse.setVolumeType(volume.getVolumeType().name()); + snapshotResponse.setVolumeState(volume.getState().name()); snapshotResponse.setVirtualSize(volume.getSize()); DataCenter zone = ApiDBUtils.findZoneById(volume.getDataCenterId()); if (zone != null) { diff --git a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java index b08fb4529f4..8b951c174f4 100644 --- a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java @@ -128,6 +128,7 @@ public class SnapshotJoinDaoImpl extends GenericDaoBaseWithTagInformation<Snapsh snapshotResponse.setVolumeId(snapshot.getVolumeUuid()); snapshotResponse.setVolumeName(snapshot.getVolumeName()); snapshotResponse.setVolumeType(snapshot.getVolumeType().name()); + snapshotResponse.setVolumeState(snapshot.getVolumeState().name()); snapshotResponse.setVirtualSize(snapshot.getVolumeSize()); VolumeVO volume = ApiDBUtils.findVolumeById(snapshot.getVolumeId()); if (volume != null && volume.getVolumeType() == Type.ROOT && volume.getInstanceId() != null) { diff --git a/server/src/main/java/com/cloud/api/query/vo/SnapshotJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/SnapshotJoinVO.java index 9ec74dac128..2dc2f7a810f 100644 --- a/server/src/main/java/com/cloud/api/query/vo/SnapshotJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/SnapshotJoinVO.java @@ -130,6 +130,10 @@ public class SnapshotJoinVO extends BaseViewWithTagInformationVO implements Cont @Enumerated(EnumType.STRING) Volume.Type volumeType = Volume.Type.UNKNOWN; + @Column(name = "volume_state") + @Enumerated(EnumType.STRING) + Volume.State volumeState; + @Column(name = "volume_size") Long volumeSize; @@ -297,6 +301,10 @@ public class SnapshotJoinVO extends BaseViewWithTagInformationVO implements Cont return volumeType; } + public Volume.State getVolumeState() { + return volumeState; + } + public Long getVolumeSize() { return volumeSize; } diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue index bb6726d75f1..98c26bf5565 100644 --- a/ui/src/components/view/InfoCard.vue +++ b/ui/src/components/view/InfoCard.vue @@ -442,7 +442,8 @@ <div class="resource-detail-item__label">{{ $t('label.volume') }}</div> <div class="resource-detail-item__details"> <hdd-outlined /> - <router-link :to="{ path: '/volume/' + resource.volumeid }">{{ resource.volumename || resource.volume || resource.volumeid }} </router-link> + <router-link v-if="validLinks.volume" :to="{ path: '/volume/' + resource.volumeid }">{{ resource.volumename || resource.volume || resource.volumeid }} </router-link> + <span v-else>{{ resource.volumename || resource.volume || resource.volumeid }}</span> </div> </div> <div class="resource-detail-item" v-if="resource.associatednetworkid"> @@ -783,6 +784,7 @@ <script> import { api } from '@/api' import { createPathBasedOnVmType } from '@/utils/plugins' +import { validateLinks } from '@/utils/links' import Console from '@/components/widgets/Console' import OsLogo from '@/components/widgets/OsLogo' import Status from '@/components/widgets/Status' @@ -848,7 +850,8 @@ export default { vpc: '', network: '' }, - newResource: {} + newResource: {}, + validLinks: {} } }, watch: { @@ -865,6 +868,7 @@ export default { this.newResource = newData this.showKeys = false this.setData() + this.validLinks = validateLinks(this.$router, this.isStatic, this.resource) if ('apikey' in this.resource) { this.getUserKeys() diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index cf3d9361638..8512dab7c05 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -167,7 +167,8 @@ <router-link :to="{ path: getVmRouteUsingType(record) + record.virtualmachineid }">{{ text }}</router-link> </template> <template v-if="column.key === 'volumename'"> - <router-link :to="{ path: '/volume/' + record.volumeid }">{{ text }}</router-link> + <router-link v-if="resourceIdToValidLinksMap[record.id]?.volume" :to="{ path: '/volume/' + record.volumeid }">{{ text }}</router-link> + <span v-else>{{ text }}</span> </template> <template v-if="column.key === 'size'"> <span v-if="text && $route.path === '/kubernetes'"> @@ -488,6 +489,7 @@ import TooltipButton from '@/components/widgets/TooltipButton' import ResourceIcon from '@/components/view/ResourceIcon' import ResourceLabel from '@/components/widgets/ResourceLabel' import { createPathBasedOnVmType } from '@/utils/plugins' +import { validateLinks } from '@/utils/links' import cronstrue from 'cronstrue/i18n' import moment from 'moment-timezone' @@ -576,6 +578,18 @@ export default { notification: 'storageallocatedthreshold', disable: 'storageallocateddisablethreshold' } + }, + resourceIdToValidLinksMap: {} + } + }, + watch: { + items: { + deep: true, + handler (newData, oldData) { + if (newData === oldData) return + this.items.forEach(record => { + this.resourceIdToValidLinksMap[record.id] = validateLinks(this.$router, false, record) + }) } } }, diff --git a/ui/src/utils/links.js b/ui/src/utils/links.js new file mode 100644 index 00000000000..fa650cdd7b8 --- /dev/null +++ b/ui/src/utils/links.js @@ -0,0 +1,36 @@ +// 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. + +export function validateLinks (router, isStatic, resource) { + const validLinks = { + volume: false + } + + if (isStatic) { + return validLinks + } + + if (resource.volumeid && router.resolve('/volume/' + resource.volumeid).matched[0].redirect !== '/exception/404') { + if (resource.volumestate) { + validLinks.volume = resource.volumestate !== 'Expunged' + } else { + validLinks.volume = true + } + } + + return validLinks +}